diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb index 314afe5..05e70a1 100644 --- a/Plain Craft Launcher 2/Application.xaml.vb +++ b/Plain Craft Launcher 2/Application.xaml.vb @@ -41,8 +41,11 @@ Public Class Application MsgBox(ex.Message, MsgBoxStyle.Critical, "内存优化失败") Environment.Exit(-1) End Try - Ram = My.Computer.Info.AvailablePhysicalMemory - Ram - Environment.Exit(Math.Max(0, Ram / 1024)) '返回清理的内存量(K) + If My.Computer.Info.AvailablePhysicalMemory < Ram Then '避免 ULong 相减出现负数 + Environment.Exit(0) + Else + Environment.Exit((My.Computer.Info.AvailablePhysicalMemory - Ram) / 1024) '返回清理的内存量(K) + End If #If DEBUG Then ElseIf e.Args(0) = "--make" Then '制作更新包 @@ -71,7 +74,8 @@ Public Class Application Directory.CreateDirectory(PathAppdata) '检测单例 #If Not DEBUG Then - Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher 2 ") + Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher ") + If WindowHwnd = IntPtr.Zero Then FindWindow(Nothing, "Plain Craft Launcher 2 ") If WindowHwnd <> IntPtr.Zero Then '将已有的 PCL 窗口拖出来 ShowWindowToTop(WindowHwnd) diff --git a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb index 792dbd1..5ca6b20 100644 --- a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb @@ -134,23 +134,21 @@ '实现自定义事件 Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp - If IsMouseDown Then - Log("[Control] 按下按钮:" & Text) - RaiseEvent Click(sender, e) - If Not String.IsNullOrEmpty(Tag) Then - If Tag.ToString.StartsWith("链接-") OrElse Tag.ToString.StartsWith("启动-") Then - Hint("主页自定义按钮语法已更新,且不再兼容老版本语法,请查看新的自定义示例!") - End If + If Not IsMouseDown Then Exit Sub + Log("[Control] 按下按钮:" & Text) + RaiseEvent Click(sender, e) + If Not String.IsNullOrEmpty(Tag) Then + If Tag.ToString.StartsWith("链接-") OrElse Tag.ToString.StartsWith("启动-") Then + Hint("主页自定义按钮语法已更新,且不再兼容老版本语法,请查看新的自定义示例!") End If - ModEvent.TryStartEvent(EventType, EventData) End If + ModEvent.TryStartEvent(EventType, EventData) End Sub Public Property EventType As String Get Return GetValue(EventTypeProperty) End Get - - Set(ByVal value As String) + Set(value As String) SetValue(EventTypeProperty, value) End Set End Property @@ -159,8 +157,7 @@ Get Return GetValue(EventDataProperty) End Get - - Set(ByVal value As String) + Set(value As String) SetValue(EventDataProperty, value) End Set End Property diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb index f6de99e..f938186 100644 --- a/Plain Craft Launcher 2/Controls/MyCard.vb +++ b/Plain Craft Launcher 2/Controls/MyCard.vb @@ -166,7 +166,9 @@ End Sub '事件 + Public Property HasMouseAnimation As Boolean = True Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter + If Not HasMouseAnimation Then Exit Sub Dim AniList As New List(Of AniData) If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 150)) If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 150)) @@ -178,6 +180,7 @@ AniStart(AniList, "MyCard Mouse " & Uuid) End Sub Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave + If Not HasMouseAnimation Then Exit Sub Dim AniList As New List(Of AniData) If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 250)) If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 250)) diff --git a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb index 1ac82e3..a31e0d0 100644 --- a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb @@ -102,7 +102,7 @@ Get Return _CanRightClick End Get - Set(ByVal value As Boolean) + Set(value As Boolean) _CanRightClick = value End Set End Property diff --git a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb index dd64166..a04d73b 100644 --- a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb @@ -34,7 +34,7 @@ Get Return LabText.Text End Get - Set(ByVal value As String) + Set(value As String) LabText.Text = value End Set End Property @@ -43,7 +43,7 @@ Get Return BtnClose.Visibility = Visibility.Visible End Get - Set(ByVal value As Boolean) + Set(value As Boolean) BtnClose.Visibility = If(value, Visibility.Visible, Visibility.Collapsed) End Set End Property diff --git a/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml new file mode 100644 index 0000000..abd786d --- /dev/null +++ b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml @@ -0,0 +1,9 @@ + + + + + + diff --git a/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb new file mode 100644 index 0000000..6176f1a --- /dev/null +++ b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb @@ -0,0 +1,180 @@ +Public Class MyIconTextButton + + '基础 + + Public Uuid As Integer = GetUuid() + Public Event Check(sender As Object, raiseByMouse As Boolean) + Public Event Change(sender As Object, raiseByMouse As Boolean) + Public Sub RaiseChange() + RaiseEvent Change(Me, False) + End Sub '使外部程序可以引发本控件的 Change 事件 + + '自定义属性 + + Public Property Logo As String + Get + Return ShapeLogo.Data.ToString + End Get + Set(value As String) + ShapeLogo.Data = (New GeometryConverter).ConvertFromString(value) + End Set + End Property + Private _LogoScale As Double = 1 + Public Property LogoScale() As Double + Get + Return _LogoScale + End Get + Set(value As Double) + _LogoScale = value + If Not IsNothing(ShapeLogo) Then ShapeLogo.RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale} + End Set + End Property + + Public Property Text As String + Get + Return GetValue(TextProperty) + End Get + Set(value As String) + SetValue(TextProperty, value) + End Set + End Property '内容 + Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(New PropertyChangedCallback( + Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs) + If Not IsNothing(sender) Then CType(sender, MyIconTextButton).LabText.Text = e.NewValue + End Sub))) + Public Enum ColorState + Black + Highlight + End Enum + Private _ColorType As ColorState = ColorState.Black + Public Property ColorType As ColorState + Get + Return _ColorType + End Get + Set(value As ColorState) + _ColorType = value + RefreshColor() + End Set + End Property '颜色类别 + + '点击事件 + + Public Event Click(sender As Object, e As RouteEventArgs) + Private IsMouseDown As Boolean = False + Private Sub MyIconTextButton_MouseUp() Handles Me.MouseLeftButtonUp + If Not IsMouseDown Then Exit Sub + Log("[Control] 按下带图标按钮:" & Text) + IsMouseDown = False + RaiseEvent Click(Me, New RouteEventArgs(True)) + ModEvent.TryStartEvent(EventType, EventData) + RefreshColor() + End Sub + Private Sub MyIconTextButton_MouseDown() Handles Me.MouseLeftButtonDown + IsMouseDown = True + RefreshColor() + End Sub + Public Property EventType As String + Get + Return GetValue(EventTypeProperty) + End Get + Set(value As String) + SetValue(EventTypeProperty, value) + End Set + End Property + Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing)) + Public Property EventData As String + Get + Return GetValue(EventDataProperty) + End Get + Set(value As String) + SetValue(EventDataProperty, value) + End Set + End Property + Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing)) + + '动画 + + Private Const AnimationTimeOfMouseIn As Integer = 100 '鼠标指向动画长度 + Private Const AnimationTimeOfMouseOut As Integer = 150 '鼠标移出动画长度 + Private Sub RefreshColor(Optional obj = Nothing, Optional e = Nothing) Handles Me.MouseEnter, Me.MouseLeave, Me.Loaded, Me.IsEnabledChanged + Try + If IsLoaded AndAlso AniControlEnabled = 0 AndAlso Not False.Equals(e) Then '防止默认属性变更触发动画,若强制不执行动画,则 e 为 False + + Select Case ColorType + Case ColorState.Black + If IsMouseDown Then + '按下 + AniStart(AaColor(Me, BackgroundProperty, "ColorBrush6", 70), "MyIconTextButton Color " & Uuid) + ElseIf IsMouseOver Then + '指向 + AniStart({ + AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrush3", AnimationTimeOfMouseIn), + AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn) + }, "MyIconTextButton Checked " & Uuid) + AniStart(AaColor(Me, BackgroundProperty, "ColorBrushBg1", AnimationTimeOfMouseIn), "MyIconTextButton Color " & Uuid) + ElseIf IsEnabled Then + '正常 + AniStart({ + AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrush1", AnimationTimeOfMouseOut), + AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush1", AnimationTimeOfMouseOut) + }, "MyIconTextButton Checked " & Uuid) + AniStart(AaColor(Me, BackgroundProperty, ColorSemiTransparent - Background, AnimationTimeOfMouseOut), "MyIconTextButton Color " & Uuid) + Else + '禁用 + AniStart({ + AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrushGray5", 100), + AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrushGray5", 100) + }, "MyIconTextButton Checked " & Uuid) + AniStart(AaColor(Me, BackgroundProperty, ColorSemiTransparent - Background, AnimationTimeOfMouseOut), "MyIconTextButton Color " & Uuid) + End If + Case ColorState.Highlight + If IsMouseDown Then + '按下 + AniStart(AaColor(Me, BackgroundProperty, "ColorBrush6", 70), "MyIconTextButton Color " & Uuid) + ElseIf IsMouseOver Then + '指向 + AniStart({ + AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrush3", AnimationTimeOfMouseIn), + AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn) + }, "MyIconTextButton Checked " & Uuid) + AniStart(AaColor(Me, BackgroundProperty, "ColorBrushBg1", AnimationTimeOfMouseIn), "MyIconTextButton Color " & Uuid) + ElseIf IsEnabled Then + '正常 + AniStart({ + AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrush3", AnimationTimeOfMouseOut), + AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseOut) + }, "MyIconTextButton Checked " & Uuid) + AniStart(AaColor(Me, BackgroundProperty, ColorSemiTransparent - Background, AnimationTimeOfMouseOut), "MyIconTextButton Color " & Uuid) + Else + '禁用 + AniStart({ + AaColor(ShapeLogo, Shapes.Path.FillProperty, "ColorBrushGray5", 100), + AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrushGray5", 100) + }, "MyIconTextButton Checked " & Uuid) + AniStart(AaColor(Me, BackgroundProperty, ColorSemiTransparent - Background, AnimationTimeOfMouseOut), "MyIconTextButton Color " & Uuid) + End If + End Select + + Else + + '不使用动画 + AniStop("MyIconTextButton Checked " & Uuid) + AniStop("MyIconTextButton Color " & Uuid) + Select Case ColorType + Case ColorState.Black + Background = ColorSemiTransparent + ShapeLogo.SetResourceReference(Shapes.Path.FillProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5")) + LabText.SetResourceReference(TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5")) + Case ColorState.Highlight + Background = ColorSemiTransparent + ShapeLogo.SetResourceReference(Shapes.Path.FillProperty, If(IsEnabled, "ColorBrush3", "ColorBrushGray5")) + LabText.SetResourceReference(TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush3", "ColorBrushGray5")) + End Select + + End If + Catch ex As Exception + Log(ex, "刷新按钮颜色出错") + End Try + End Sub + +End Class diff --git a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb index f026386..ea0e10f 100644 --- a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb @@ -15,7 +15,7 @@ If _RectBack Is Nothing Then Dim Rect As New Border With { .Name = "RectBack", - .CornerRadius = New CornerRadius(If(IsScaleAnimationEnabled, 6, 0)), + .CornerRadius = New CornerRadius(If(IsScaleAnimationEnabled OrElse Height > 40, 6, 0)), .RenderTransform = If(IsScaleAnimationEnabled, New ScaleTransform(0.8, 0.8), Nothing), .RenderTransformOrigin = New Point(0.5, 0.5), .BorderThickness = New Thickness(GetWPFSize(1)), @@ -225,6 +225,7 @@ If _Logo.ToLower.StartsWith("http") Then '网络图片 PathLogo = New Image With { + .Tag = Me, .IsHitTestVisible = LogoClickable, .Source = New ImageSourceConverter().ConvertFromString(_Logo), .RenderTransformOrigin = New Point(0.5, 0.5), @@ -235,6 +236,7 @@ '位图 Dim Bitmap = New MyBitmap(_Logo) PathLogo = New Canvas With { + .Tag = Me, .IsHitTestVisible = LogoClickable, .Background = Bitmap, .RenderTransformOrigin = New Point(0.5, 0.5), @@ -252,6 +254,7 @@ Else '矢量图 PathLogo = New Shapes.Path With { + .Tag = Me, .IsHitTestVisible = LogoClickable, .HorizontalAlignment = HorizontalAlignment.Center, .VerticalAlignment = VerticalAlignment.Center, .Stretch = Stretch.Uniform, .Data = (New GeometryConverter).ConvertFromString(_Logo), .RenderTransformOrigin = New Point(0.5, 0.5), @@ -267,7 +270,7 @@ If LogoClickable Then AddHandler PathLogo.MouseLeave, Sub(sender, e) IsLogoDown = False AddHandler PathLogo.MouseLeftButtonDown, Sub(sender, e) IsLogoDown = True - AddHandler PathLogo.MouseLeftButtonUp, Sub(sender, e) If IsLogoDown Then IsLogoDown = False : RaiseEvent LogoClick(sender, e) + AddHandler PathLogo.MouseLeftButtonUp, Sub(sender, e) If IsLogoDown Then IsLogoDown = False : RaiseEvent LogoClick(sender.Tag, e) End If End If '改变行距 @@ -286,6 +289,9 @@ End Property '图标的点击 + ''' + ''' 该 Logo 是否可用点击触发事件。需要在 Logo 属性之前设置。 + ''' Public Property LogoClickable As Boolean = False Private IsLogoDown As Boolean = False @@ -447,7 +453,7 @@ RectCheck.VerticalAlignment = VerticalAlignment.Center RectCheck.Margin = New Thickness(-1, 0, 0, 0) End If - Anim.Add(AaColor(Me, ForegroundProperty, "ColorBrush3", 200)) + Anim.Add(AaColor(Me, ForegroundProperty, If(Height < 40, "ColorBrush3", "ColorBrush2"), 200)) Else '由有变无 If Not IsNothing(RectCheck) Then @@ -469,7 +475,7 @@ RectCheck.Opacity = 1 RectCheck.VerticalAlignment = VerticalAlignment.Stretch End If - SetResourceReference(ForegroundProperty, "ColorBrush3") + SetResourceReference(ForegroundProperty, If(Height < 40, "ColorBrush3", "ColorBrush2")) Else If Not IsNothing(RectCheck) Then RectCheck.Height = 0 @@ -615,12 +621,14 @@ Else If ButtonStack IsNot Nothing Then Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.5)) Ani.Add(AaOpacity(RectBack, -RectBack.Opacity, Time)) - If IsScaleAnimationEnabled Then Ani.AddRange({ - AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrush7"), Time), - AaScaleTransform(Me, 1 - CType(Me.RenderTransform, ScaleTransform).ScaleX, Time * 3,, New AniEaseOutFluent), - AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent), - AaScaleTransform(RectBack, -0.246, 1,,, True) - }) + If IsScaleAnimationEnabled Then + Ani.AddRange({ + AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrush7"), Time), + AaScaleTransform(Me, 1 - CType(RenderTransform, ScaleTransform).ScaleX, Time * 3,, New AniEaseOutFluent), + AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent), + AaScaleTransform(RectBack, -0.246, 1,,, True) + }) + End If End If AniStart(Ani, "ListItem Color " & Uuid) Else @@ -647,7 +655,7 @@ Private Sub MyListItem_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded If Checked Then - SetResourceReference(ForegroundProperty, "ColorBrush3") + SetResourceReference(ForegroundProperty, If(Height < 40, "ColorBrush3", "ColorBrush2")) Else SetResourceReference(ForegroundProperty, "ColorBrush1") End If diff --git a/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb b/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb index 8fca7bd..8a595b6 100644 --- a/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb @@ -46,7 +46,7 @@ Public Class MyLoading Get Return _Text End Get - Set(ByVal value As String) + Set(value As String) _Text = value RefreshText() End Set @@ -57,7 +57,7 @@ Public Class MyLoading Get Return _TextError End Get - Set(ByVal value As String) + Set(value As String) _TextError = value RefreshText() End Set diff --git a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml index aa42f53..e5753e0 100644 --- a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml +++ b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml @@ -4,6 +4,6 @@ HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27"> - + diff --git a/Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb b/Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb index c8ccf8d..ee3bb15 100644 --- a/Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb @@ -1,7 +1,7 @@ Class MySearchBox Inherits MyCard - Public Event TextChanged(ByVal sender As Object, ByVal e As EventArgs) + Public Event TextChanged(sender As Object, e As EventArgs) Private Sub MySearchBox_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded TextBox.Focus() End Sub @@ -11,7 +11,7 @@ Get Return TextBox.HintText End Get - Set(ByVal value As String) + Set(value As String) TextBox.HintText = value End Set End Property @@ -19,7 +19,7 @@ Get Return TextBox.Text End Get - Set(ByVal value As String) + Set(value As String) TextBox.Text = value End Set End Property diff --git a/Plain Craft Launcher 2/Controls/MyTextButton.vb b/Plain Craft Launcher 2/Controls/MyTextButton.vb index 9b56fa8..3d8245f 100644 --- a/Plain Craft Launcher 2/Controls/MyTextButton.vb +++ b/Plain Craft Launcher 2/Controls/MyTextButton.vb @@ -91,7 +91,7 @@ Return GetValue(EventTypeProperty) End Get - Set(ByVal value As String) + Set(value As String) SetValue(EventTypeProperty, value) End Set End Property @@ -101,7 +101,7 @@ Return GetValue(EventDataProperty) End Get - Set(ByVal value As String) + Set(value As String) SetValue(EventDataProperty, value) End Set End Property diff --git a/Plain Craft Launcher 2/FormMain.xaml b/Plain Craft Launcher 2/FormMain.xaml index ea433ba..c54903e 100644 --- a/Plain Craft Launcher 2/FormMain.xaml +++ b/Plain Craft Launcher 2/FormMain.xaml @@ -4,7 +4,7 @@ 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:Name="WindMain" x:Class="FormMain" Style="{StaticResource MyWindow}" - AllowDrop="True" Topmost="True" Title="Plain Craft Launcher 2 " AllowsTransparency="True" MinHeight="470" MinWidth="810" ScrollViewer.VerticalScrollBarVisibility="Disabled" Icon="/Plain Craft Launcher 2;component/Images/icon.ico" WindowStyle="None" ResizeMode="CanMinimize" Background="{x:Null}" d:DesignWidth="870" d:DesignHeight="520" Width="850" Height="500" RenderTransformOrigin="0.5,0.5"> + AllowDrop="True" Topmost="True" Title="Plain Craft Launcher " AllowsTransparency="True" MinHeight="470" MinWidth="810" ScrollViewer.VerticalScrollBarVisibility="Disabled" Icon="/Plain Craft Launcher 2;component/Images/icon.ico" WindowStyle="None" ResizeMode="CanMinimize" Background="{x:Null}" d:DesignWidth="870" d:DesignHeight="520" Width="850" Height="500" RenderTransformOrigin="0.5,0.5"> diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index a9a4611..ba3bcac 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -116,8 +116,13 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- - If LastVersion < 297 Then 'Snapshot 2.6.3 + If LastVersion < 298 Then 'Snapshot 2.6.4 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 管理中允许多选 Mod 进行批量操作")) FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化崩溃分析,添加多种崩溃情况的判断")) + FeatureCount += 18 + BugCount += 19 + End If + If LastVersion < 297 Then 'Snapshot 2.6.3 FeatureCount += 12 BugCount += 14 End If @@ -744,31 +749,32 @@ Public Class FormMain Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Critical) Exit Sub End If - If McVersionCurrent Is Nothing Then + Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent) + If TargetVersion Is Nothing Then Hint("请先下载游戏,再设置第三方登录!", HintType.Critical) Exit Sub End If If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then 'LittleSkin - If MyMsgBox($"是否要在版本 {McVersionCurrent.Name} 中开启 LittleSkin 登录?" & vbCrLf & + If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启 LittleSkin 登录?" & vbCrLf & "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then Exit Sub End If - Setup.Set("VersionServerLogin", 4, Version:=McVersionCurrent) - Setup.Set("VersionServerAuthServer", "https://littleskin.cn/api/yggdrasil", Version:=McVersionCurrent) - Setup.Set("VersionServerAuthRegister", "https://littleskin.cn/auth/register", Version:=McVersionCurrent) - Setup.Set("VersionServerAuthName", "LittleSkin 登录", Version:=McVersionCurrent) + 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) Else '第三方 Authlib 服务器 - If MyMsgBox($"是否要在版本 {McVersionCurrent.Name} 中开启第三方登录?" & vbCrLf & + If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启第三方登录?" & vbCrLf & $"登录服务器:{AuthlibServer}" & vbCrLf & vbCrLf & "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then Exit Sub End If - Setup.Set("VersionServerLogin", 4, Version:=McVersionCurrent) - Setup.Set("VersionServerAuthServer", AuthlibServer, Version:=McVersionCurrent) - Setup.Set("VersionServerAuthRegister", AuthlibServer.Replace("api/yggdrasil", "auth/register"), Version:=McVersionCurrent) - Setup.Set("VersionServerAuthName", "", Version:=McVersionCurrent) + 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) End If If PageCurrent = PageType.VersionSetup AndAlso PageCurrentSub = PageSubType.VersionSetup Then '正在服务器选项页,需要刷新设置项显示 @@ -916,7 +922,7 @@ Install: Get Return _Hidden End Get - Set(ByVal value As Boolean) + Set(value As Boolean) If _Hidden = value Then Exit Property _Hidden = value If value Then @@ -963,6 +969,7 @@ Install: #Region "切换页面" '页面种类与属性 + '注意,这一枚举在 “切换页面” EventType 中调用,应视作公开 API 的一部分 ''' ''' 页面种类。 ''' @@ -970,43 +977,43 @@ Install: ''' ''' 启动。 ''' - Launch + Launch = 0 ''' ''' 下载。 ''' - Download + Download = 1 ''' ''' 联机。 ''' - Link + Link = 2 ''' ''' 设置。 ''' - Setup + Setup = 3 ''' ''' 更多。 ''' - Other + Other = 4 ''' ''' 版本选择。这是一个副页面。 ''' - VersionSelect + VersionSelect = 5 ''' ''' 下载管理。这是一个副页面。 ''' - DownloadManager + DownloadManager = 6 ''' ''' 版本设置。这是一个副页面。 ''' - VersionSetup + VersionSetup = 7 ''' ''' 资源工程详情。这是一个副页面。 ''' - CompDetail + CompDetail = 8 ''' ''' 帮助详情。这是一个副页面。 ''' - HelpDetail + HelpDetail = 9 End Enum ''' ''' 次要页面种类。其数值必须与 StackPanel 中的下标一致。 diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index a40eb48..5d2f6da 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -11,12 +11,12 @@ Public Module ModBase #Region "声明" '下列版本信息由更新器自动修改 - Public Const VersionBaseName As String = "2.6.3" '不含分支前缀的显示用版本名 - Public Const VersionStandardCode As String = "2.6.3." & VersionBranchCode '标准格式的四段式版本号 + Public Const VersionBaseName As String = "2.6.4" '不含分支前缀的显示用版本名 + Public Const VersionStandardCode As String = "2.6.4." & VersionBranchCode '标准格式的四段式版本号 #If BETA Then - Public Const VersionCode As Integer = 293 'Release + Public Const VersionCode As Integer = 296 'Release #Else - Public Const VersionCode As Integer = 297 'Snapshot + Public Const VersionCode As Integer = 298 'Snapshot #End If '自动生成的版本信息 Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName diff --git a/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb b/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb index 7cbe59f..02da732 100644 --- a/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb +++ b/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb @@ -73,10 +73,10 @@ Public Class MyBitmap Pic = My.Application.TryFindResource(FilePathOrResourceName) If Pic Is Nothing Then Pic = New System.Drawing.Bitmap(1, 1) - Log(ex, "加载位图失败(" & FilePathOrResourceName & ")") + Log(ex, $"加载位图失败({FilePathOrResourceName})") Throw Else - Log(ex, "指定类型有误的位图加载(" & FilePathOrResourceName & ")", LogLevel.Developer) + Log(ex, $"指定类型有误的位图加载({FilePathOrResourceName})", LogLevel.Developer) Exit Try End If End Try diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb index 400087c..fbfd8e4 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb @@ -375,6 +375,7 @@ End Select Next If Tags.Count = 0 Then Tags.Add("杂项") + Tags.Sort() ModLoaders.Sort() #End Region End If @@ -424,15 +425,32 @@ Dim ExNameList As List(Of String) If TranslatedName = RawName Then '没有中文翻译 - FinalName = TranslatedName.Split(" | ").First.Split(" - ").First.Split("(").First.Split("[").First - ExNameList = TranslatedName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries). - Select(Function(s) s.Trim(" /".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList - ExNameList.RemoveAt(0) - If Not ExNameList.Any(Function(s) s.ToLower.Contains("forge") OrElse s.ToLower.Contains("fabric") OrElse s.ToLower.Contains("quilt")) AndAlso '不是标注 XX 版 - Not (ExNameList.Count = 1 AndAlso ExNameList.First.ToUpper = ExNameList.First) Then '不是缩写 - FinalName = RawName '使用原名 - ExNameList.Clear() - End If + '将所有名称分段 + Dim NameLists = TranslatedName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries). + Select(Function(s) s.Trim(" /\".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList + If NameLists.Count = 1 Then GoTo NoExName + '查找其中的缩写、Forge/Fabric 等版本标记 + ExNameList = New List(Of String) + Dim NormalNameList = New List(Of String) + For Each Name In NameLists + Dim LowerName As String = Name.ToLower + If Name.ToUpper = Name Then + '缩写 + ExNameList.Add(Name) + ElseIf (LowerName.Contains("forge") OrElse LowerName.Contains("fabric") OrElse LowerName.Contains("quilt")) AndAlso + Not RegexCheck(LowerName.Replace("forge", "").Replace("fabric", "").Replace("quilt", ""), "[a-z]+") Then '去掉关键词后没有其他字母 + 'Forge/Fabric 等版本标记 + ExNameList.Add(Name) + Else + '其他部分 + NormalNameList.Add(Name) + End If + Next + '根据分类后的结果处理 + If NormalNameList.Count = 0 OrElse ExNameList.Count = 0 Then GoTo NoExName + '同时包含 NormalName 和 ExName + FinalName = NormalNameList.Join(" - ") + ExNameList = ExNameList Else '有中文翻译 '尝试将文本分为三段:FinalName (EnglishName) - Suffix @@ -452,21 +470,20 @@ ExNameList = New List(Of String) From {EnglishName} '使用原名 End If '添加后缀 - If Suffix <> "" Then - ExNameList.Add(Suffix) - ExNameList = ExNameList.Distinct().ToList() - End If + If Suffix <> "" Then ExNameList.Add(Suffix) End If + ExNameList = ExNameList.Distinct().ToList() '显示 FinalName 与 ExNameList - NewItem.LabTitle.Text = FinalName If ExNameList.Count = 0 Then NoExName: + NewItem.LabTitle.Text = RawName CType(NewItem.LabTitleRaw.Parent, StackPanel).Children.Remove(NewItem.LabTitleRaw) Else + NewItem.LabTitle.Text = FinalName Dim ExName As String = "" For Each Ex In ExNameList '去除 “Forge/Fabric” 这一无意义提示 - If Ex.Length < 19 AndAlso Ex.ToLower.Contains("fabric") AndAlso Ex.ToLower.Contains("forge") Then Continue For + If Ex.Length < 16 AndAlso Ex.ToLower.Contains("fabric") AndAlso Ex.ToLower.Contains("forge") Then Continue For '将 “Forge” 等提示改为 “Forge 版” If (Ex.ToLower.Contains("forge") OrElse Ex.ToLower.Contains("fabric") OrElse Ex.ToLower.Contains("quilt")) AndAlso Not Ex.Contains("版") AndAlso Ex.ToLower.Replace("forge", "").Replace("fabric", "").Replace("quilt", "").Length <= 3 Then diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb index 0d45097..1dc2251 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb @@ -365,7 +365,7 @@ ShadersMod与Optifine同时安装 Forge安装不完整 Mod需要Java11 - Mod依赖项未完善 + Mod缺少前置或MC版本错误 End Enum ''' ''' 根据 AnalyzeLogs 与可能的版本信息分析崩溃原因。 @@ -374,9 +374,11 @@ Log("[Crash] 步骤 3:分析崩溃原因") LogAll = (If(LogMc, "") & If(LogHs, "") & If(LogCrash, "")).ToLower - '1. 精准日志匹配 1 + '1. 精准日志匹配,中/高优先级 AnalyzeCrit1() If CrashReasons.Count > 0 Then GoTo Done + AnalyzeCrit2() + If CrashReasons.Count > 0 Then GoTo Done '2. 堆栈分析 If LogAll.Contains("forge") OrElse LogAll.Contains("fabric") OrElse LogAll.Contains("liteloader") Then @@ -414,8 +416,8 @@ Log("[Crash] 可能并未安装 Mod,不进行堆栈分析") End If - '3. 精准日志匹配 2 - AnalyzeCrit2() + '3. 精准日志匹配,低优先级 + AnalyzeCrit3() '输出到日志 Done: @@ -470,13 +472,12 @@ Done: If LogMc.Contains("java.lang.ClassCastException: java.base/jdk") Then AppendReason(CrashReason.使用JDK) If LogMc.Contains("java.lang.ClassCastException: class jdk.") Then AppendReason(CrashReason.使用JDK) If LogMc.Contains("TRANSFORMER/net.optifine/net.optifine.reflect.Reflector.(Reflector.java") Then AppendReason(CrashReason.OptiFine与Forge不兼容) - If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.texture.SpriteContents.(net.minecraft.resources.ResourceLocation, ") Then AppendReason(CrashReason.OptiFine与Forge不兼容) + If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.texture.SpriteContents.") Then AppendReason(CrashReason.OptiFine与Forge不兼容) If LogMc.Contains("java.lang.NoSuchMethodError: 'java.lang.String com.mojang.blaze3d.systems.RenderSystem.getBackendDescription") Then AppendReason(CrashReason.OptiFine与Forge不兼容) - If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.block.model.BakedQuad.(int[], int, net.minecraft.core.Direction, net.minecraft.client.renderer.texture.TextureAtlasSprite, boolean, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)'1.19.2 Optifine HD_U_H9 Forge 43.2.17 - If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.addRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容) - If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.removeRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容) - If LogMc.Contains("java.lang.NoSuchMethodError: 'net.minecraft.network.chat.FormattedText net.minecraft.client.gui.Font.ellipsize(net.minecraft.network.chat.FormattedText, int)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容) - If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraftforge.client.gui.overlay.ForgeGui.renderSelectedItemName(net.minecraft.client.gui.GuiGraphics, int)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)'1.20.1 OptiFine HD_U_I5 Forge 47.1.28/47.1.14 , 1.20.1 OptiFine_I6_pre1 Forge 47.1.14 + If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.block.model.BakedQuad.") Then AppendReason(CrashReason.OptiFine与Forge不兼容) + If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraftforge.client.gui.overlay.ForgeGui.renderSelectedItemName") Then AppendReason(CrashReason.OptiFine与Forge不兼容) + If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager") Then AppendReason(CrashReason.OptiFine与Forge不兼容) + If LogMc.Contains("java.lang.NoSuchMethodError: 'net.minecraft.network.chat.FormattedText net.minecraft.client.gui.Font.ellipsize") Then AppendReason(CrashReason.OptiFine与Forge不兼容) If LogMc.Contains("Open J9 is not supported") OrElse LogMc.Contains("OpenJ9 is incompatible") OrElse LogMc.Contains(".J9VMInternals.") Then AppendReason(CrashReason.使用OpenJ9) If LogMc.Contains("java.lang.NoSuchFieldException: ucp") Then AppendReason(CrashReason.Java版本过高) If LogMc.Contains("because module java.base does not export") Then AppendReason(CrashReason.Java版本过高) @@ -498,7 +499,7 @@ Done: If LogMc.Contains("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符) If LogMc.Contains("has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to") Then AppendReason(CrashReason.Mod需要Java11) If LogMc.Contains("java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual") Then AppendReason(CrashReason.Mod需要Java11) - If LogMc.Contains("java.lang.UnsupportedClassVersionError: net/fabricmc/loader/impl/launch/knot/KnotClient : Unsupported major.minor version") Then AppendReason(CrashReason.Java版本不兼容) + If LogMc.Contains("Unsupported major.minor version") Then AppendReason(CrashReason.Java版本不兼容) If LogMc.Contains("Invalid maximum heap size") Then AppendReason(CrashReason.使用32位Java导致JVM无法分配足够多的内存) If LogMc.Contains("Could not reserve enough space") Then If LogMc.Contains("for 1048576KB object heap") Then @@ -507,34 +508,17 @@ Done: AppendReason(CrashReason.内存不足) End If End If + '确定的 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("Found a duplicate mod") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "Found a duplicate mod[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase)) If LogMc.Contains("ModResolutionException: Duplicate") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "ModResolutionException: Duplicate[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase)) - 'Mod 导致的崩溃 - If LogMc.Contains("Mixin prepare failed ") OrElse LogMc.Contains("Mixin apply failed ") OrElse LogMc.Contains("MixinApplyError") OrElse - LogMc.Contains("mixin.injection.throwables.") OrElse LogMc.Contains(".mixins.json] FAILED during )") Then - Dim ModId As String = RegexSeek(LogMc, "(?<=in )[^./ ]+(?=.mixins.json.+failed injection check)") - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=in mixins.)[^./ ]+(?=.json.+failed injection check)") - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in )[^./ ]+(?=.mixins.json)") - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in mixins.)[^./ ]+(?=.json)") - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )") - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[mixins.)[^./ ]+(?=.json\] FAILED during )") - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=from mod )[^./ ]+(?=\] from)") - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=for mod )[^./ ]+(?= failed)") - '兜底名称判断 - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "[^./ \]]+(?=.mixins.json") - If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=mixins.)[^./ \]]+(?=.json") - AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(If(ModId, "").TrimEnd((vbCrLf & " ").ToCharArray))) - 'Mod 依赖项未完善 - If LogCrash.Contains("Missing or unsupported mandatory dependencies:") Then - AppendReason(CrashReason.Mod依赖项未完善, RegexSearch(LogMc, "(?<=Missing or unsupported mandatory dependencies:)[\n\r]*(\t(.*)[\n\r]*)+"), "").TrimEnd((vbCrLf & " ").ToCharArray) - End If - If LogMc.Contains("Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(RegexSeek(LogMc, "(?<=Caught exception from )[^\n]+?")?.TrimEnd((vbCrLf & " ").ToCharArray))) - '仅当没有其他分析结果时显示 - If CrashReasons.Count = 0 Then - If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf)) - If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf)) + 'Mod 缺少前置 + If LogMc.Contains("Missing or unsupported mandatory dependencies:") Then + AppendReason(CrashReason.Mod缺少前置或MC版本错误, + RegexSearch(LogMc, "(?<=Missing or unsupported mandatory dependencies:)([\n\r]+\t(.*))+", RegularExpressions.RegexOptions.IgnoreCase). + Select(Function(s) s.Trim((vbCrLf & vbTab & " ").ToCharArray)).Distinct().ToList()) End If End If @@ -572,14 +556,43 @@ Done: End Sub ''' - ''' 进行精准日志匹配。匹配优先级低于堆栈分析的崩溃。 + ''' 进行精准日志匹配。匹配优先级高于堆栈分析的崩溃,但低于上面的。 + ''' 如果第一步已经找到了原因则不执行该检测。 ''' Private Sub AnalyzeCrit2() '游戏日志分析 If LogMc IsNot Nothing Then - If Not (LogMc.Contains("at net.") OrElse LogMc.Contains("INFO]")) AndAlso - LogHs Is Nothing AndAlso LogCrash Is Nothing AndAlso LogMc.Length < 100 Then + 'Mixin 崩溃 + If LogMc.Contains("Mixin prepare failed ") OrElse LogMc.Contains("Mixin apply failed ") OrElse LogMc.Contains("MixinApplyError") OrElse + LogMc.Contains("mixin.injection.throwables.") OrElse LogMc.Contains(".mixins.json] FAILED during )") Then + Dim ModId As String = RegexSeek(LogMc, "(?<=in )[^./ ]+(?=.mixins.json.+failed injection check)") + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=in mixins.)[^./ ]+(?=.json.+failed injection check)") + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in )[^./ ]+(?=.mixins.json)") + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in mixins.)[^./ ]+(?=.json)") + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )") + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[mixins.)[^./ ]+(?=.json\] FAILED during )") + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=from mod )[^./ ]+(?=\] from)") + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=for mod )[^./ ]+(?= failed)") + '兜底名称判断 + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "[^./ \]]+(?=.mixins.json") + If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=mixins.)[^./ \]]+(?=.json") + AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(If(ModId, "").TrimEnd((vbCrLf & " ").ToCharArray))) + End If + '常规信息 + If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf)) + If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf)) + End If + + End Sub + ''' + ''' 进行精准日志匹配。匹配优先级低于堆栈分析的崩溃。 + ''' + Private Sub AnalyzeCrit3() + + '游戏日志分析 + If LogMc IsNot Nothing Then + If Not (LogMc.Contains("at net.") OrElse LogMc.Contains("INFO]")) AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing AndAlso LogMc.Length < 100 Then AppendReason(CrashReason.极短的程序输出, LogMc) End If 'Mixin 失败可以导致大量 Mod 实例创建失败 @@ -814,7 +827,7 @@ NextStack: Case CrashReason.Mod文件被解压 Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。") Case CrashReason.内存不足 - Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是由于你为游戏分配的内存不足,或是游戏的配置要求过高。\n\n你可以在启动设置中增加为游戏分配的内存,删除配置要求较高的材质、Mod、光影。\n如果这依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h") + Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是因为电脑内存不足、游戏分配的内存不足,或是配置要求过高。\n\n你可以尝试在 更多 → 百宝箱 中选择 内存优化,然后再启动游戏。\n如果还是不行,请在启动设置中增加为游戏分配的内存,并删除配置要求较高的材质、Mod、光影。\n如果依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h") Case CrashReason.使用OpenJ9 Results.Add("游戏因为使用 Open J9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。") Case CrashReason.使用JDK @@ -837,6 +850,12 @@ NextStack: Else Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了以下可疑的关键词:\n - " & Join(Additional, ", ") & "\n\n如果你知道这些关键词对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h") End If + Case CrashReason.Mod缺少前置或MC版本错误 + If Additional.Count > 0 Then + Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n未满足的依赖项:\n - " & Join(Additional, "\n - ") & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。") + Else + Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h") + End If Case CrashReason.崩溃日志堆栈分析发现Mod名称 If Additional.Count = 1 Then Results.Add("名为 " & Additional.First & " 的 Mod 可能导致了游戏出错。\n\e\h") @@ -943,12 +962,6 @@ NextStack: Else Results.Add("Forge 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h") End If - Case CrashReason.Mod依赖项未完善 - If Additional.Count = 1 Then - Results.Add("由于 Mod 缺少或不受支持的必备依赖项,导致了游戏退出。\n以下是未满足条件的必备依赖项:" & Additional.First & "\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。") - Else - Results.Add("由于 Mod 缺少或不受支持的强制依赖项,导致了游戏退出。请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h") - End If Case CrashReason.没有可用的分析文件 Results.Add("你的游戏出现了一些问题,但 PCL 未能找到相关记录文件,因此无法进行分析。\h") Case Else diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb index 7c6acf0..81a31f9 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb @@ -1,5 +1,5 @@ Public Module ModJava - Public JavaListCacheVersion As Integer = 4 + Public JavaListCacheVersion As Integer = 5 ''' ''' 目前所有可用的 Java。 @@ -129,8 +129,10 @@ End If Is64Bit = Output.Contains("64-bit") If Version.Minor <= 4 OrElse Version.Minor >= 25 Then Throw New Exception("分析详细信息失败,获取的版本为 " & Version.ToString) - '无论如何不允许使用 JRE 16,它完全是炸的,安 OptiFine、Forge 和启动 MC 都会炸 - If IsJre AndAlso VersionCode = 16 Then Throw New Exception("由于 JRE 16 对 Minecraft 的兼容性很差,因此不再允许使用") + '基于 #2249 发现的 JRE 17 似乎也导致了 Forge 安装失败,干脆禁用更多版本的 JRE + If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对 Minecraft 的兼容性很差,因此不再允许使用") + ''无论如何不允许使用 JRE 16,它完全是炸的,安 OptiFine、Forge 和启动 MC 都会炸 + 'If IsJre AndAlso VersionCode = 16 Then Throw New Exception("由于 JRE 16 对 Minecraft 的兼容性很差,因此不再允许使用") Catch ex As Exception Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出")) Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex) @@ -230,6 +232,8 @@ Try UserJava = JavaEntry.FromJson(GetJson(Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion))) UserJava.Check() + Catch ex As ThreadInterruptedException + Throw Catch ex As Exception UserJava = Nothing Setup.Reset("VersionArgumentJavaSelect", Version:=RelatedVersion) @@ -242,6 +246,8 @@ Try UserJava = JavaEntry.FromJson(GetJson(Setup.Get("LaunchArgumentJavaSelect"))) UserJava.Check() + Catch ex As ThreadInterruptedException + Throw Catch ex As Exception UserJava = Nothing Setup.Reset("LaunchArgumentJavaSelect") @@ -450,7 +456,7 @@ NoUserJava: '4. Java 大版本 If Left.VersionCode <> Right.VersionCode Then ' Java 7 8 9 10 11 12 13 14 15 16 17 18 19 20... - Dim Weight = {0, 1, 2, 3, 4, 5, 6, 14, 29, 10, 12, 15, 13, 9, 8, 7, 11, 31, 30, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28} + Dim Weight = {0, 1, 2, 3, 4, 5, 6, 14, 30, 10, 12, 15, 13, 9, 8, 7, 11, 31, 29, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28} Return Weight.ElementAtOrDefault(Left.VersionCode) >= Weight.ElementAtOrDefault(Right.VersionCode) End If '5. 最次级版本号更接近 51 @@ -530,10 +536,10 @@ NoUserJava: For Each Pair In JavaPreList If Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") Then Log("[Java] 位于 " & Pair.Key & " 的 Java 包含特殊引用") - Continue For + Else + Log("[Java] 位于 " & Pair.Key & " 的 Java 不含特殊引用") + JavaWithoutInherit.Add(Pair.Key, Pair.Value) End If - Log("[Java] 位于 " & Pair.Key & " 的 Java 不含特殊引用") - JavaWithoutInherit.Add(Pair.Key, Pair.Value) Next If JavaWithoutInherit.Count > 0 Then JavaPreList = JavaWithoutInherit @@ -688,18 +694,17 @@ Wait: ''' ''' 提示 Java 缺失,并弹窗确认是否自动下载。返回玩家选择是否下载。 - ''' 支持下载 Java 8、14、17,输入其他 VersionCode 会要求玩家自行下载。 ''' - Public Function JavaDownloadConfirm(VersionCode As String) As Boolean - If VersionCode = 8 OrElse VersionCode = 14 OrElse VersionCode = 17 Then - Return MyMsgBox($"PCL 未找到 Java {VersionCode},是否需要 PCL 自动下载?" & vbCrLf & - $"如果你已经安装了 Java {VersionCode},请在 设置 → 启动选项 → 游戏 Java 中手动导入。", - "未找到 Java", "自动下载", "取消") = 1 - Else - MyMsgBox($"PCL 未找到 Java {VersionCode}。" & vbCrLf & - $"请自行搜索并安装 Java {VersionCode},安装后在 设置 → 启动选项 → 游戏 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 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb index 569cc1b..3bb3b84 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb @@ -1131,11 +1131,11 @@ SystemBrowser: If McVersionCurrent.Version.McName = "1.7.2" Then '1.7.2:必须 Java 7 MinVer = New Version(1, 7, 0, 0) : MaxVer = New Version(1, 7, 999, 999) - ElseIf McVersionCurrent.Version.McCodeMain <= 12 AndAlso McVersionCurrent.Version.McCodeMain > 0 AndAlso VersionSortBoolean("14.23.5.2855", McVersionCurrent.Version.ForgeVersion) Then - '<=1.12,Forge 14.23.5.2855 及更低:Java 8 + ElseIf McVersionCurrent.Version.McCodeMain <= 12 AndAlso McVersionCurrent.Version.McCodeMain > 0 Then + '<=1.12:Java 8 MaxVer = New Version(1, 8, 999, 999) - ElseIf McVersionCurrent.Version.McCodeMain <= 14 AndAlso McVersionCurrent.Version.McCodeMain >= 13 AndAlso VersionSortBoolean("28.2.23", McVersionCurrent.Version.ForgeVersion) Then - '1.13 - 1.14,Forge 28.2.23 及更低:Java 8 - 10 + ElseIf McVersionCurrent.Version.McCodeMain <= 14 AndAlso McVersionCurrent.Version.McCodeMain >= 13 Then + '1.13 - 1.14:Java 8 - 10 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 10, 999, 999) ElseIf McVersionCurrent.Version.McCodeMain = 15 Then '1.15:Java 8 - 15 @@ -1182,17 +1182,27 @@ SystemBrowser: If Task.IsAborted Then Exit Sub '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java McLaunchLog("无合适的 Java,需要确认是否自动下载") Dim JavaCode As String - If MinVer >= New Version(1, 16, 0, 0) Then + If MinVer >= New Version(1, 9, 0, 0) Then JavaCode = 17 + If Not JavaDownloadConfirm("Java 17") Then Throw New Exception("$$") ElseIf MaxVer < New Version(1, 8, 0, 0) Then JavaCode = 7 - ElseIf MinVer > New Version(1, 8, 0, 52) Then - '缺少 Java 8 较新版(统一通行证) + If Not JavaDownloadConfirm("Java 7") Then Throw New Exception("$$") + ElseIf MinVer > New Version(1, 8, 0, 100) AndAlso MaxVer < New Version(1, 8, 0, 321) Then JavaCode = "8u101" + JavaDownloadConfirm("Java 8.0.101 ~ 8.0.320", True) + Throw New Exception("$$") + ElseIf MinVer > New Version(1, 8, 0, 100) Then + JavaCode = "8u101" + JavaDownloadConfirm("Java 8.0.101 或更高版本的 Java 8", True) + 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("$$") Else JavaCode = 8 + If Not JavaDownloadConfirm("Java 8") Then Throw New Exception("$$") End If - If Not JavaDownloadConfirm(JavaCode) Then Throw New Exception("$$") '开始自动下载 Dim JavaLoader = JavaFixLoaders(JavaCode) @@ -1350,7 +1360,7 @@ SystemBrowser: '添加 Java Wrapper 作为主 Jar If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED") - DataList.Insert(0, "-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """") + DataList.Add("-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """") DataList.Add("-jar """ & ExtractJavaWrapper() & """") '添加 MainClass @@ -1417,7 +1427,7 @@ NextVersion: '添加 Java Wrapper 作为主 Jar If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED") - DataList.Insert(0, "-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """") + DataList.Add("-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """") DataList.Add("-jar """ & ExtractJavaWrapper() & """") '将 "-XXX" 与后面 "XXX" 合并到一起 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb index 9c8dc16..7be9392 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb @@ -1,4 +1,5 @@ Imports System.IO.Compression +Imports NAudio.Gui Public Module ModMinecraft @@ -392,7 +393,7 @@ Public Module ModMinecraft '非准确的版本判断警告 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})?)?)") + 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 @@ -401,7 +402,7 @@ Public Module ModMinecraft 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})?)?)") + 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 @@ -467,7 +468,7 @@ VersionSearchFinish: End If Return _JsonText End Get - Set(ByVal value As String) + Set(value As String) _JsonText = value End Set End Property @@ -562,7 +563,7 @@ Recheck: End If Return _JsonObject End Get - Set(ByVal value As JObject) + Set(value As JObject) _JsonObject = value End Set End Property @@ -966,7 +967,7 @@ ExitDataLoad: End If Return _SortCode End Get - Set(ByVal value As Integer) + Set(value As Integer) _SortCode = value End Set End Property @@ -1608,7 +1609,7 @@ NextVersion: Get Return _Url End Get - Set(ByVal value As String) + Set(value As String) '孤儿 Forge 作者喜欢把没有的 URL 写个空字符串 _Url = If(String.IsNullOrWhiteSpace(value), Nothing, value) End Set @@ -2122,30 +2123,46 @@ NextVersion: '确认 Virtual 与 Map 状态 Dim IsVirtual As Boolean = False - If Json("virtual") IsNot Nothing Then IsVirtual = Json("virtual").ToString - Dim IsMap As Boolean = False - If Json("map_to_resources") IsNot Nothing Then IsMap = Json("map_to_resources").ToString + If Json("virtual") IsNot Nothing AndAlso Json("virtual").ToString Then IsVirtual = True + If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToString Then + IsVirtual = True + '刷新 resources 文件夹符号链接(#2182) + 'Dim Info As FileSystemInfo = New FileInfo(PathMcFolder & "resources\pack.mcmeta") + 'If Info.Attributes.HasFlag(FileAttributes.ReparsePoint) Then + If Not File.Exists(PathMcFolder & "resources\pack.mcmeta") Then + Log("[Minecraft] 尝试刷新 resources 文件夹符号链接", LogLevel.Debug) + Try + DeleteDirectory(PathMcFolder & "resources\", True) + Directory.CreateDirectory(PathMcFolder & "assets\virtual\legacy\") + Dim Result = ShellAndGetOutput("cmd", $"/C mklink /D /J ""{PathMcFolder}resources"" ""{PathMcFolder}assets\virtual\legacy""") + Log($"[Minecraft] 符号链接创建结果:{Result}") + If Not Result.Contains("<<===>>") Then Throw New Exception($"非预期的结果({Result})") + Catch ex As Exception + Log(ex, "创建资源文件夹链接失败,游戏可能会没有声音", LogLevel.Msgbox) + End Try + End If + End If '加载列表 - If IsVirtual OrElse IsMap Then + If IsVirtual Then For Each File As JProperty In Json("objects").Children McAssetsListGet.Add(New McAssetsToken With { - .IsVirtual = True, - .LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\"), - .SourcePath = File.Name, - .Hash = File.Value("hash").ToString, - .Size = File.Value("size").ToString - }) + .IsVirtual = True, + .LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\"), + .SourcePath = File.Name, + .Hash = File.Value("hash").ToString, + .Size = File.Value("size").ToString + }) Next Else For Each File As JProperty In Json("objects").Children McAssetsListGet.Add(New McAssetsToken With { - .IsVirtual = False, - .LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString, - .SourcePath = File.Name, - .Hash = File.Value("hash").ToString, - .Size = File.Value("size").ToString - }) + .IsVirtual = False, + .LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString, + .SourcePath = File.Name, + .Hash = File.Value("hash").ToString, + .Size = File.Value("size").ToString + }) Next End If @@ -2186,899 +2203,6 @@ NextVersion: Return Result End Function -#End Region - -#Region "Mod" - - Public Class McMod - -#Region "基础" - - ''' - ''' Mod 文件的地址。 - ''' - Public ReadOnly Path As String - Public Sub New(Path As String) - Me.Path = If(Path, "") - End Sub - - ''' - ''' Mod 的完整文件名。 - ''' - Public ReadOnly Property FileName As String - Get - Return GetFileNameFromPath(Path) - End Get - End Property - - ''' - ''' Mod 的状态。 - ''' - Public ReadOnly Property State As McModState - Get - Load() - If Not IsFileAvailable Then - Return McModState.Unavaliable - ElseIf Path.EndsWith(".disabled") Then - Return McModState.Disabled - Else - Return McModState.Fine - End If - End Get - End Property - Public Enum McModState As Integer - Fine = 0 - Disabled = 1 - Unavaliable = 2 - End Enum - -#End Region - -#Region "信息项" - - ''' - ''' Mod 的名称。若不可用则为 ModID 或无扩展的文件名。 - ''' - Public Property Name As String - Get - If _Name Is Nothing Then Load() - If _Name Is Nothing Then _Name = _ModId - If _Name Is Nothing Then _Name = GetFileNameWithoutExtentionFromPath(Path) - Return _Name - End Get - Set(value As String) - If _Name Is Nothing AndAlso value IsNot Nothing AndAlso - value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then - _Name = value - End If - End Set - End Property - Private _Name As String = Nothing - - ''' - ''' Mod 的描述信息。 - ''' - 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) - If _Description Is Nothing AndAlso value IsNot Nothing AndAlso value.Count > 2 Then - _Description = value.ToString.Trim(vbLf) - '优化显示:若以 [a-zA-Z0-9] 结尾,加上小数点句号 - If _Description.ToLower.LastIndexOfAny("qwertyuiopasdfghjklzxcvbnm0123456789") = _Description.Count - 1 Then _Description += "." - End If - End Set - End Property - Private _Description As String = Nothing - - ''' - ''' Mod 的版本,不保证符合版本格式规范。 - ''' - Public Property Version As String - Get - If _Version Is Nothing Then Load() - Return _Version - End Get - Set(value As String) - If _Version IsNot Nothing AndAlso (_Version.Contains(".") OrElse _Version.Contains("-")) Then Exit Property - If value IsNot Nothing AndAlso value.ToLower.Contains("version") Then value = "version" '需要修改的标识 - _Version = value - End Set - End Property - Private _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 Exit Property - 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) - If _ModId Is Nothing Then _ModId = value - End If - 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.StartsWith("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 Exit Sub - ModID = ModID.ToLower - If ModID = "name" OrElse Val(ModID).ToString = ModID Then Exit Sub '跳过 name 与纯数字 id - If VersionRequirement Is Nothing OrElse ((Not VersionRequirement.Contains(".")) AndAlso (Not VersionRequirement.Contains("-"))) OrElse VersionRequirement.Contains("$") Then - VersionRequirement = Nothing - Else - If (Not VersionRequirement.StartsWith("[")) AndAlso (Not VersionRequirement.StartsWith("(")) AndAlso (Not VersionRequirement.EndsWith("]")) AndAlso (Not VersionRequirement.EndsWith(")")) 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 "加载" - - ''' - ''' 初始化所有数据。 - ''' - 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 - IsInfoWithoutClassAvailable = False - IsInfoWithClassLoaded = False - IsInfoWithClassAvailable = False - End Sub - - ''' - ''' 进行文件可用性检查与 .class 以外的信息获取。 - ''' - Public Sub Load(Optional ForceReload As Boolean = False) - If IsLoaded AndAlso Not ForceReload Then Exit Sub - '初始化 - Init() - '阶段 1:基础可用性检查 - Dim Jar As ZipArchive - Try - '打开 Jar 文件 - If Path.Length < 2 Then Throw New FileNotFoundException("错误的 Mod 文件路径(" & If(Path, "null") & ")") - If Not File.Exists(Path) Then Throw New FileNotFoundException("未找到 Mod 文件(" & Path & ")") - Jar = New ZipArchive(New FileStream(Path, FileMode.Open)) - If Jar.Entries.Count = 0 Then Throw New FileFormatException("文件内容为空") - Catch ex As UnauthorizedAccessException - Log(ex, "Mod 文件由于无权限无法打开(" & Path & ")", LogLevel.Developer) - _FileUnavailableReason = New UnauthorizedAccessException("没有读取此文件的权限,请尝试右键以管理员身份运行 PCL", ex) - Jar = Nothing - Catch ex As Exception - Log(ex, "Mod 文件无法打开(" & Path & ")", LogLevel.Developer) - _FileUnavailableReason = ex - Jar = Nothing - End Try - '阶段 2:信息获取 - If Jar IsNot Nothing Then LoadWithoutClass(Jar) - '阶段 3: Class 信息获取 - If Jar IsNot Nothing AndAlso Not IsInfoWithoutClassAvailable Then LoadWithClass(Jar) - '释放文件 - Try - If Jar IsNot Nothing Then Jar.Dispose() - Catch - End Try - '完成标记 - IsLoaded = True - End Sub - - ''' - ''' 进行不使用 .class 文件的信息获取。 - ''' - Private Sub LoadWithoutClass(Jar As ZipArchive) - -#Region "尝试使用 mcmod.info" - Try - '获取信息文件 - Dim InfoEntry As ZipArchiveEntry = Jar.GetEntry("mcmod.info") - Dim InfoString As String = Nothing - If InfoEntry IsNot Nothing Then - InfoString = ReadFile(InfoEntry.Open()) - If InfoString.Length < 15 Then InfoString = Nothing - End If - If InfoString Is Nothing Then Exit Try - '获取可用 Json 项 - Dim InfoObject As JObject - Dim JsonObject = GetJson(InfoString) - If JsonObject.Type = JTokenType.Array Then - InfoObject = JsonObject(0) - Else - InfoObject = JsonObject("modList")(0) - End If - '从文件中获取 Mod 信息项 - Name = 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.Count > 0 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.IndexOf(":") + 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.IndexOf(":") + 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 -#End Region -#Region "尝试使用 mods.toml" - Try - '获取 mods.toml 文件 - Dim TomlEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/mods.toml") - Dim TomlText As String = Nothing - If TomlEntry IsNot Nothing Then - TomlText = ReadFile(TomlEntry.Open()) - If TomlText.Length < 15 Then TomlText = Nothing - End If - If TomlText Is Nothing Then Exit Try - '文件标准化:统一换行符为 vbLf,去除注释、头尾的空格、空行 - Dim Lines As New List(Of String) - For Each Line In TomlText.Replace(vbCrLf, vbLf).Replace(vbCr, vbLf).Split(vbLf) '统一换行符 - If Line.StartsWith("#") Then '去除注释 - Continue For - ElseIf Line.Contains("#") Then - Line = Line.Substring(0, Line.IndexOf("#")) - End If - Line = Line.Trim(New Char() {" "c, " "c, " "c}) '去除头尾的空格 - If Line.Count > 0 Then Lines.Add(Line) '去除空行 - Next - '读取文件数据 - Dim TomlData As New List(Of KeyValuePair(Of String, Dictionary(Of String, Object))) From {New KeyValuePair(Of String, Dictionary(Of String, Object))("", New Dictionary(Of String, Object))} - For i = 0 To Lines.Count - 1 - Dim Line As String = Lines(i) - If Line.StartsWith("[[") AndAlso Line.EndsWith("]]") Then - '段落标记 - Dim Header = Line.Replace("[", "").Replace("]", "") - TomlData.Add(New KeyValuePair(Of String, Dictionary(Of String, Object))(Header, New Dictionary(Of String, Object))) - ElseIf Line.Contains("=") Then - '字段标记 - Dim Key As String = Line.Substring(0, Line.IndexOf("=")).TrimEnd(New Char() {" "c, " "c, " "c}) - Dim RawValue As String = Line.Substring(Line.IndexOf("=") + 1).TrimStart(New Char() {" "c, " "c, " "c}) - Dim Value As Object - If RawValue.StartsWith("""") AndAlso RawValue.EndsWith("""") Then - '单行字符串 - Value = RawValue.Trim("""") - ElseIf RawValue.StartsWith("'''") Then - '多行字符串 - Dim ValueLines As New List(Of String) From {RawValue.TrimStart("'")} - Do Until i >= Lines.Count - 1 - i += 1 - Dim ValueLine As String = Lines(i) - If ValueLine.EndsWith("'''") Then - ValueLines.Add(ValueLine.TrimEnd("'")) - Exit Do - Else - ValueLines.Add(ValueLine) - End If - Loop - Value = Join(ValueLines, vbLf).Trim(vbLf).Replace(vbLf, vbCrLf) - ElseIf RawValue.ToLower = "true" OrElse RawValue.ToLower = "false" Then - '布尔型 - Value = (RawValue.ToLower = "true") - ElseIf Val(RawValue).ToString = RawValue Then - '数字型 - Value = Val(RawValue) - Else - '不知道是个啥玩意儿,直接存储 - Value = RawValue - End If - TomlData.Last.Value(Key) = Value - Else - '不知道是个啥玩意儿 - Exit Try - End If - Next - '从文件数据中获取信息 - Dim ModEntry As Dictionary(Of String, Object) = Nothing - For Each TomlSubData In TomlData - If TomlSubData.Key = "mods" Then - ModEntry = TomlSubData.Value - Exit For - 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("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.StartsWith("dependencies") 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 - Log(ex, "读取 mods.toml 时出现未知错误(" & Path & ")", LogLevel.Developer) - End Try -#End Region -#Region "尝试使用 fabric.mod.json" - Try - '获取 fabric.mod.json 文件 - Dim FabricEntry As ZipArchiveEntry = Jar.GetEntry("fabric.mod.json") - Dim FabricText As String = Nothing - If FabricEntry IsNot Nothing Then - FabricText = ReadFile(FabricEntry.Open(), Encoding.UTF8) - If Not FabricText.Contains("schemaVersion") Then FabricText = Nothing - End If - If FabricText Is Nothing Then Exit Try - Dim FabricObject As JObject = GetJson(FabricText) -GotFabric: - '从文件中获取 Mod 信息项 - If FabricObject.ContainsKey("name") Then Name = 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.Count > 0 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.StartsWith("required-") Then Continue For - ' Dep = Dep.Substring(Dep.IndexOf(":") + 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, "读取 fabric.mod.json 时出现未知错误(" & Path & ")", LogLevel.Developer) - End Try -#End Region -#Region "尝试使用 fml_cache_annotation.json" - Try - '获取 fml_cache_annotation.json 文件 - Dim FmlEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/fml_cache_annotation.json") - Dim FmlText As String = Nothing - If FmlEntry IsNot Nothing Then - FmlText = ReadFile(FmlEntry.Open(), Encoding.UTF8) - If Not FmlText.Contains("Lnet/minecraftforge/fml/common/Mod;") Then FmlText = Nothing - End If - If FmlText Is Nothing Then Exit Try - Dim FmlJson As JObject = GetJson(FmlText) - '获取可用 Json 项 - Dim FmlObject As JObject = Nothing - For Each ModFilePair In FmlJson - Dim ModFileAnnos As JArray = ModFilePair.Value("annotations") - If ModFileAnnos IsNot Nothing Then - '先获取 Mod - For Each ModFileAnno In ModFileAnnos - Dim Name As String = If(ModFileAnno("name"), "") - If Name = "Lnet/minecraftforge/fml/common/Mod;" Then - FmlObject = ModFileAnno("values") - GoTo Got - End If - Next - End If - Next - 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("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.StartsWith("required-") Then Continue For - Dep = Dep.Substring(Dep.IndexOf(":") + 1) - If Dep.Contains("@") Then - AddDependency(Dep.Split("@")(0), Dep.Split("@")(1)) - Else - AddDependency(Dep) - End If - Next - End If - End If - Catch ex As Exception - Log(ex, "读取 fml_cache_annotation.json 时出现未知错误(" & Path & ")", LogLevel.Developer) - End Try -#End Region -Finished: -#Region "将 Version 代号转换为 META-INF 中的版本" - If _Version = "version" Then - Try - Dim MetaEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/MANIFEST.MF") - If MetaEntry IsNot Nothing Then - Dim MetaString As String = ReadFile(MetaEntry.Open()).Replace(" :", ":").Replace(": ", ":") - If MetaString.Contains("Implementation-Version:") Then - MetaString = MetaString.Substring(MetaString.IndexOf("Implementation-Version:") + "Implementation-Version:".Count) - MetaString = MetaString.Substring(0, MetaString.IndexOfAny(vbCrLf.ToCharArray)).Trim - Version = MetaString - End If - End If - Catch ex As Exception - Log("获取 META-INF 中的版本信息失败(" & Path & ")", LogLevel.Developer) - Version = Nothing - End Try - End If - If _Version IsNot Nothing AndAlso Not (_Version.Contains(".") OrElse _Version.Contains("-")) Then Version = Nothing -#End Region - - IsInfoWithoutClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing - End Sub - - ''' - ''' 进行使用 .class 文件的信息获取。 - ''' - Private Sub LoadWithClass(Jar As ZipArchive) - Try - '查找入口点文件 - Dim ModClass As String = Nothing - Dim ModClassNotBest As String = Nothing '非完美匹配 - For Each Entry In Jar.Entries - If Entry.Name.EndsWith(".class") Then - Dim Temp As String = ReadFile(Entry.Open()).ToLower - If Temp.Contains("#lnet/minecraftforge/fml/common/mod;") Then - ModClass = Temp - Exit For - ElseIf Temp.Contains("modid") Then - ModClassNotBest = Temp - End If - End If - Next - If ModClass Is Nothing Then ModClass = ModClassNotBest - If ModClass Is Nothing Then Throw New FileNotFoundException("未找到 Mod 入口点") - ModClass = ModClass.Replace("ljava/lang/string;", "") - If ModClass.Count > 3000 Then ModClass = ModClass.Substring(0, 3000) '如果文件过大,截取前 3000 Byte - Dim IndexHead As Integer, IndexTail As Integer, IncreaseCount As Integer - '获取 ModID - If _ModId Is Nothing Then - IndexHead = ModClass.IndexOf("modid") + "modid".Length + 1 - IncreaseCount = 0 - Do While Convert.ToInt32(ModClass(IndexHead)) < 32 - IndexHead += 1 - IncreaseCount += 1 - If IncreaseCount > 10 Then Throw New Exception("ModID 头匹配失败") - Loop - IndexTail = IndexHead + 1 - IncreaseCount = 0 - Do Until Convert.ToInt32(ModClass(IndexTail)) < 32 - IndexTail += 1 - IncreaseCount += 1 - If IncreaseCount > 50 Then Throw New Exception("ModID 尾匹配失败") - Loop - ModId = ModClass.Substring(IndexHead, IndexTail - IndexHead) - End If - '获取 Version - If _Version Is Nothing AndAlso ModClass.Contains("version") Then - IndexHead = ModClass.IndexOf("version") + "version".Length + 1 - IncreaseCount = 0 - Do While Convert.ToInt32(ModClass(IndexHead)) < 32 - IndexHead += 1 - IncreaseCount += 1 - If IncreaseCount > 10 Then GoTo VersionFindFail - Loop - IndexTail = IndexHead + 1 - IncreaseCount = 0 - Do While ModClass(IndexTail) = "."c OrElse ModClass(IndexTail) = "-"c OrElse - (ModClass(IndexTail) >= "0"c AndAlso ModClass(IndexTail) <= "9"c) OrElse - (ModClass(IndexTail) >= "a"c AndAlso ModClass(IndexTail) <= "z"c) - IndexTail += 1 - IncreaseCount += 1 - If IncreaseCount > 50 Then GoTo VersionFindFail - Loop - _Version = ModClass.Substring(IndexHead, IndexTail - IndexHead) - End If -VersionFindFail: - '获取 Dependencies - IndexHead = ModClass.IndexOf("dependencies") - If IndexHead > 0 Then - If ModClass.Count >= IndexHead + 300 Then ModClass = ModClass.Substring(IndexHead, 299) - Dim Deps As List(Of String) = RegexSearch(ModClass, "(?<=required-((before|after|before-client|after-client)?):)[0-9a-z]+(@[\(\[]{1}[0-9.,]+[\)\]]{1})?") - For Each Token As String In Deps - If Not String.IsNullOrEmpty(Token) Then - 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, "Mod Class 信息不可用(" & If(Path, "null") & ")", LogLevel.Normal) - End Try - IsInfoWithClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing - End Sub - -#End Region - - ''' - ''' 是否可能为前置 Mod。 - ''' - Public Function IsPresetMod() As Boolean - Return Dependencies.Count = 0 AndAlso (Name IsNot Nothing) AndAlso (Name.ToLower.Contains("core") OrElse Name.ToLower.Contains("lib")) - End Function - - ''' - ''' 根据完整文件路径的文件扩展名判断是否为 Mod 文件。 - ''' - Public Shared Function IsModFile(Path As String) - If Path Is Nothing OrElse Not Path.Contains(".") Then Return False - Path = Path.ToLower - If Path.EndsWith(".jar") OrElse Path.EndsWith(".zip") OrElse Path.EndsWith(".litemod") OrElse - Path.EndsWith(".jar.disabled") OrElse Path.EndsWith(".zip.disabled") OrElse Path.EndsWith(".litemod.disabled") Then Return True - Return False - End Function - - End Class - - '加载 Mod 列表 - 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) - - '获取 Mod 文件夹下的可用文件列表 - Dim ModFileList As New List(Of FileInfo) - If Directory.Exists(Loader.Input) Then - Dim RawName As String = Loader.Input.ToLower - 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 - Continue For - End If - End If - If McMod.IsModFile(File.FullName) Then ModFileList.Add(File) - Next - End If - - '确定是否显示进度 - Loader.Progress = 0.03 - If ModFileList.Count > 100 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True) - - '加载为 Mod 列表 - Dim ModList As New List(Of McMod) - For Each ModFile As FileInfo In ModFileList - Dim ModEntity As New McMod(ModFile.FullName) - ModEntity.Load() - ModList.Add(ModEntity) - Loader.Progress += 0.9 / ModFileList.Count - Next - Loader.Progress = 0.93 - - '排序 - ModList = Sort(ModList, Function(Left As McMod, Right As McMod) As Boolean - If (Left.State = McMod.McModState.Unavaliable) <> (Right.State = McMod.McModState.Unavaliable) Then - Return Left.State = McMod.McModState.Unavaliable - Else - Return Left.FileName < Right.FileName - End If - End Function) - Loader.Progress = 0.98 - - '回设 - If Loader.IsAborted Then Exit Sub - Loader.Output = ModList - - Catch ex As Exception - Log(ex, "Mod 列表加载失败", LogLevel.Debug) - Throw - End Try - End Sub - -#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.StartsWith("[") - Dim ReqVersionTailCanEqual As Boolean = ReqVersion.EndsWith("]") - 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.StartsWith("1.") AndAlso ReqVersionHead.Contains("-") Then ReqVersionHead = ReqVersionHead.Substring(ReqVersionHead.LastIndexOf("-") + 1) - If ReqVersionTail.StartsWith("1.") AndAlso ReqVersionTail.Contains("-") Then ReqVersionTail = ReqVersionTail.Substring(ReqVersionTail.LastIndexOf("-") + 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.StartsWith("1.") AndAlso CurrentVersion.Contains("-") Then CurrentVersion = CurrentVersion.Substring(CurrentVersion.LastIndexOf("-") + 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 时出错:" & GetExceptionSummary(ex) & vbCrLf & " - " & ModEntity.FileName) - Log(ex, "检查 Mod 时出错") - End Try - Next - If Result.Count = 0 Then - Log("[Minecraft] Mod 检查未发现异常") - Else - Log("[Minecraft] Mod 检查异常结果:" & vbCrLf & Join(Result, vbCrLf)) - End If - Return Result - End Function -#End If - #End Region ''' diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb new file mode 100644 index 0000000..27a31aa --- /dev/null +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb @@ -0,0 +1,903 @@ +Imports System.IO.Compression + +Public Module ModMod + + Public Class McMod + +#Region "基础" + + ''' + ''' Mod 文件的地址。 + ''' + Public ReadOnly Path As String + Public Sub New(Path As String) + Me.Path = If(Path, "") + End Sub + + ''' + ''' Mod 的完整文件名。 + ''' + Public ReadOnly Property FileName As String + Get + Return GetFileNameFromPath(Path) + End Get + End Property + + ''' + ''' Mod 的完整文件名,去除最后的 .disabled。 + ''' + Public ReadOnly Property RawFileName As String + Get + Return FileName.Replace(".disabled", "") + End Get + End Property + + ''' + ''' Mod 的状态。 + ''' + Public ReadOnly Property State As McModState + Get + Load() + If Not IsFileAvailable Then + Return McModState.Unavaliable + ElseIf Path.EndsWith(".disabled") Then + Return McModState.Disabled + Else + Return McModState.Fine + End If + End Get + End Property + Public Enum McModState As Integer + Fine = 0 + Disabled = 1 + Unavaliable = 2 + End Enum + +#End Region + +#Region "信息项" + + ''' + ''' Mod 的名称。若不可用则为 ModID 或无扩展的文件名。 + ''' + Public Property Name As String + Get + If _Name Is Nothing Then Load() + If _Name Is Nothing Then _Name = _ModId + If _Name Is Nothing Then _Name = GetFileNameWithoutExtentionFromPath(Path) + Return _Name + End Get + Set(value As String) + If _Name Is Nothing AndAlso value IsNot Nothing AndAlso + value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then + _Name = value + End If + End Set + End Property + Private _Name As String = Nothing + + ''' + ''' Mod 的描述信息。 + ''' + 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) + If _Description Is Nothing AndAlso value IsNot Nothing AndAlso value.Count > 2 Then + _Description = value.ToString.Trim(vbLf) + '优化显示:若以 [a-zA-Z0-9] 结尾,加上小数点句号 + If _Description.ToLower.LastIndexOfAny("qwertyuiopasdfghjklzxcvbnm0123456789") = _Description.Count - 1 Then _Description += "." + End If + End Set + End Property + Private _Description As String = Nothing + + ''' + ''' Mod 的版本,不保证符合版本格式规范。 + ''' + Public Property Version As String + Get + If _Version Is Nothing Then Load() + Return _Version + End Get + Set(value As String) + If _Version IsNot Nothing AndAlso (_Version.Contains(".") OrElse _Version.Contains("-")) Then Exit Property + If value IsNot Nothing AndAlso value.ToLower.Contains("version") Then value = "version" '需要修改的标识 + _Version = value + End Set + End Property + Private _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 Exit Property + 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) + If _ModId Is Nothing Then _ModId = value + End If + 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.StartsWith("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 Exit Sub + ModID = ModID.ToLower + If ModID = "name" OrElse Val(ModID).ToString = ModID Then Exit Sub '跳过 name 与纯数字 id + If VersionRequirement Is Nothing OrElse ((Not VersionRequirement.Contains(".")) AndAlso (Not VersionRequirement.Contains("-"))) OrElse VersionRequirement.Contains("$") Then + VersionRequirement = Nothing + Else + If (Not VersionRequirement.StartsWith("[")) AndAlso (Not VersionRequirement.StartsWith("(")) AndAlso (Not VersionRequirement.EndsWith("]")) AndAlso (Not VersionRequirement.EndsWith(")")) 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 "加载" + + ''' + ''' 初始化所有数据。 + ''' + 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 + IsInfoWithoutClassAvailable = False + IsInfoWithClassLoaded = False + IsInfoWithClassAvailable = False + End Sub + + ''' + ''' 进行文件可用性检查与 .class 以外的信息获取。 + ''' + Public Sub Load(Optional ForceReload As Boolean = False) + If IsLoaded AndAlso Not ForceReload Then Exit Sub + '初始化 + Init() + '阶段 1:基础可用性检查 + Dim Jar As ZipArchive + Try + '打开 Jar 文件 + If Path.Length < 2 Then Throw New FileNotFoundException("错误的 Mod 文件路径(" & If(Path, "null") & ")") + If Not File.Exists(Path) Then Throw New FileNotFoundException("未找到 Mod 文件(" & Path & ")") + Jar = New ZipArchive(New FileStream(Path, FileMode.Open)) + If Jar.Entries.Count = 0 Then Throw New FileFormatException("文件内容为空") + Catch ex As UnauthorizedAccessException + Log(ex, "Mod 文件由于无权限无法打开(" & Path & ")", LogLevel.Developer) + _FileUnavailableReason = New UnauthorizedAccessException("没有读取此文件的权限,请尝试右键以管理员身份运行 PCL", ex) + Jar = Nothing + Catch ex As Exception + Log(ex, "Mod 文件无法打开(" & Path & ")", LogLevel.Developer) + _FileUnavailableReason = ex + Jar = Nothing + End Try + '阶段 2:信息获取 + If Jar IsNot Nothing Then LoadWithoutClass(Jar) + '阶段 3: Class 信息获取 + If Jar IsNot Nothing AndAlso Not IsInfoWithoutClassAvailable Then LoadWithClass(Jar) + '释放文件 + Try + If Jar IsNot Nothing Then Jar.Dispose() + Catch + End Try + '完成标记 + IsLoaded = True + End Sub + + ''' + ''' 进行不使用 .class 文件的信息获取。 + ''' + Private Sub LoadWithoutClass(Jar As ZipArchive) + +#Region "尝试使用 mcmod.info" + Try + '获取信息文件 + Dim InfoEntry As ZipArchiveEntry = Jar.GetEntry("mcmod.info") + Dim InfoString As String = Nothing + If InfoEntry IsNot Nothing Then + InfoString = ReadFile(InfoEntry.Open()) + If InfoString.Length < 15 Then InfoString = Nothing + End If + If InfoString Is Nothing Then Exit Try + '获取可用 Json 项 + Dim InfoObject As JObject + Dim JsonObject = GetJson(InfoString) + If JsonObject.Type = JTokenType.Array Then + InfoObject = JsonObject(0) + Else + InfoObject = JsonObject("modList")(0) + End If + '从文件中获取 Mod 信息项 + Name = 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.Count > 0 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.IndexOf(":") + 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.IndexOf(":") + 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 +#End Region +#Region "尝试使用 mods.toml" + Try + '获取 mods.toml 文件 + Dim TomlEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/mods.toml") + Dim TomlText As String = Nothing + If TomlEntry IsNot Nothing Then + TomlText = ReadFile(TomlEntry.Open()) + If TomlText.Length < 15 Then TomlText = Nothing + End If + If TomlText Is Nothing Then Exit Try + '文件标准化:统一换行符为 vbLf,去除注释、头尾的空格、空行 + Dim Lines As New List(Of String) + For Each Line In TomlText.Replace(vbCrLf, vbLf).Replace(vbCr, vbLf).Split(vbLf) '统一换行符 + If Line.StartsWith("#") Then '去除注释 + Continue For + ElseIf Line.Contains("#") Then + Line = Line.Substring(0, Line.IndexOf("#")) + End If + Line = Line.Trim(New Char() {" "c, " "c, " "c}) '去除头尾的空格 + If Line.Count > 0 Then Lines.Add(Line) '去除空行 + Next + '读取文件数据 + Dim TomlData As New List(Of KeyValuePair(Of String, Dictionary(Of String, Object))) From {New KeyValuePair(Of String, Dictionary(Of String, Object))("", New Dictionary(Of String, Object))} + For i = 0 To Lines.Count - 1 + Dim Line As String = Lines(i) + If Line.StartsWith("[") AndAlso Line.EndsWith("]") Then + '段落标记 + Dim Header = Line.Trim("[]".ToCharArray) + TomlData.Add(New KeyValuePair(Of String, Dictionary(Of String, Object))(Header, New Dictionary(Of String, Object))) + ElseIf Line.Contains("=") Then + '字段标记 + Dim Key As String = Line.Substring(0, Line.IndexOf("=")).TrimEnd(New Char() {" "c, " "c, " "c}) + Dim RawValue As String = Line.Substring(Line.IndexOf("=") + 1).TrimStart(New Char() {" "c, " "c, " "c}) + Dim Value As Object + If RawValue.StartsWith("""") AndAlso RawValue.EndsWith("""") Then + '单行字符串 + Value = RawValue.Trim("""") + ElseIf RawValue.StartsWith("'''") Then + '多行字符串 + Dim ValueLines As New List(Of String) From {RawValue.TrimStart("'")} + Do Until i >= Lines.Count - 1 + i += 1 + Dim ValueLine As String = Lines(i) + If ValueLine.EndsWith("'''") Then + ValueLines.Add(ValueLine.TrimEnd("'")) + Exit Do + Else + ValueLines.Add(ValueLine) + End If + Loop + Value = Join(ValueLines, vbLf).Trim(vbLf).Replace(vbLf, vbCrLf) + ElseIf RawValue.ToLower = "true" OrElse RawValue.ToLower = "false" Then + '布尔型 + Value = (RawValue.ToLower = "true") + ElseIf Val(RawValue).ToString = RawValue Then + '数字型 + Value = Val(RawValue) + Else + '不知道是个啥玩意儿,直接存储 + Value = RawValue + End If + TomlData.Last.Value(Key) = Value + Else + '不知道是个啥玩意儿 + Exit Try + End If + Next + '从文件数据中获取信息 + Dim ModEntry As Dictionary(Of String, Object) = Nothing + For Each TomlSubData In TomlData + If TomlSubData.Key = "mods" Then + ModEntry = TomlSubData.Value + Exit For + 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("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 + Log(ex, "读取 mods.toml 时出现未知错误(" & Path & ")", LogLevel.Developer) + End Try +#End Region +#Region "尝试使用 fabric.mod.json" + Try + '获取 fabric.mod.json 文件 + Dim FabricEntry As ZipArchiveEntry = Jar.GetEntry("fabric.mod.json") + Dim FabricText As String = Nothing + If FabricEntry IsNot Nothing Then + FabricText = ReadFile(FabricEntry.Open(), Encoding.UTF8) + If Not FabricText.Contains("schemaVersion") Then FabricText = Nothing + End If + If FabricText Is Nothing Then Exit Try + Dim FabricObject As JObject = GetJson(FabricText) +GotFabric: + '从文件中获取 Mod 信息项 + If FabricObject.ContainsKey("name") Then Name = 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.Count > 0 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.StartsWith("required-") Then Continue For + ' Dep = Dep.Substring(Dep.IndexOf(":") + 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, "读取 fabric.mod.json 时出现未知错误(" & Path & ")", LogLevel.Developer) + End Try +#End Region +#Region "尝试使用 fml_cache_annotation.json" + Try + '获取 fml_cache_annotation.json 文件 + Dim FmlEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/fml_cache_annotation.json") + Dim FmlText As String = Nothing + If FmlEntry IsNot Nothing Then + FmlText = ReadFile(FmlEntry.Open(), Encoding.UTF8) + If Not FmlText.Contains("Lnet/minecraftforge/fml/common/Mod;") Then FmlText = Nothing + End If + If FmlText Is Nothing Then Exit Try + Dim FmlJson As JObject = GetJson(FmlText) + '获取可用 Json 项 + Dim FmlObject As JObject = Nothing + For Each ModFilePair In FmlJson + Dim ModFileAnnos As JArray = ModFilePair.Value("annotations") + If ModFileAnnos IsNot Nothing Then + '先获取 Mod + For Each ModFileAnno In ModFileAnnos + Dim Name As String = If(ModFileAnno("name"), "") + If Name = "Lnet/minecraftforge/fml/common/Mod;" Then + FmlObject = ModFileAnno("values") + GoTo Got + End If + Next + End If + Next + 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("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.StartsWith("required-") Then Continue For + Dep = Dep.Substring(Dep.IndexOf(":") + 1) + If Dep.Contains("@") Then + AddDependency(Dep.Split("@")(0), Dep.Split("@")(1)) + Else + AddDependency(Dep) + End If + Next + End If + End If + Catch ex As Exception + Log(ex, "读取 fml_cache_annotation.json 时出现未知错误(" & Path & ")", LogLevel.Developer) + End Try +#End Region +Finished: +#Region "将 Version 代号转换为 META-INF 中的版本" + If _Version = "version" Then + Try + Dim MetaEntry As ZipArchiveEntry = Jar.GetEntry("META-INF/MANIFEST.MF") + If MetaEntry IsNot Nothing Then + Dim MetaString As String = ReadFile(MetaEntry.Open()).Replace(" :", ":").Replace(": ", ":") + If MetaString.Contains("Implementation-Version:") Then + MetaString = MetaString.Substring(MetaString.IndexOf("Implementation-Version:") + "Implementation-Version:".Count) + MetaString = MetaString.Substring(0, MetaString.IndexOfAny(vbCrLf.ToCharArray)).Trim + Version = MetaString + End If + End If + Catch ex As Exception + Log("获取 META-INF 中的版本信息失败(" & Path & ")", LogLevel.Developer) + Version = Nothing + End Try + End If + If _Version IsNot Nothing AndAlso Not (_Version.Contains(".") OrElse _Version.Contains("-")) Then Version = Nothing +#End Region + + IsInfoWithoutClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing + End Sub + + ''' + ''' 进行使用 .class 文件的信息获取。 + ''' + Private Sub LoadWithClass(Jar As ZipArchive) + Try + '查找入口点文件 + Dim ModClass As String = Nothing + Dim ModClassNotBest As String = Nothing '非完美匹配 + For Each Entry In Jar.Entries + If Entry.Name.EndsWith(".class") Then + Dim Temp As String = ReadFile(Entry.Open()).ToLower + If Temp.Contains("#lnet/minecraftforge/fml/common/mod;") Then + ModClass = Temp + Exit For + ElseIf Temp.Contains("modid") Then + ModClassNotBest = Temp + End If + End If + Next + If ModClass Is Nothing Then ModClass = ModClassNotBest + If ModClass Is Nothing Then Throw New FileNotFoundException("未找到 Mod 入口点") + ModClass = ModClass.Replace("ljava/lang/string;", "") + If ModClass.Count > 3000 Then ModClass = ModClass.Substring(0, 3000) '如果文件过大,截取前 3000 Byte + Dim IndexHead As Integer, IndexTail As Integer, IncreaseCount As Integer + '获取 ModID + If _ModId Is Nothing Then + IndexHead = ModClass.IndexOf("modid") + "modid".Length + 1 + IncreaseCount = 0 + Do While Convert.ToInt32(ModClass(IndexHead)) < 32 + IndexHead += 1 + IncreaseCount += 1 + If IncreaseCount > 10 Then Throw New Exception("ModID 头匹配失败") + Loop + IndexTail = IndexHead + 1 + IncreaseCount = 0 + Do Until Convert.ToInt32(ModClass(IndexTail)) < 32 + IndexTail += 1 + IncreaseCount += 1 + If IncreaseCount > 50 Then Throw New Exception("ModID 尾匹配失败") + Loop + ModId = ModClass.Substring(IndexHead, IndexTail - IndexHead) + End If + '获取 Version + If _Version Is Nothing AndAlso ModClass.Contains("version") Then + IndexHead = ModClass.IndexOf("version") + "version".Length + 1 + IncreaseCount = 0 + Do While Convert.ToInt32(ModClass(IndexHead)) < 32 + IndexHead += 1 + IncreaseCount += 1 + If IncreaseCount > 10 Then GoTo VersionFindFail + Loop + IndexTail = IndexHead + 1 + IncreaseCount = 0 + Do While ModClass(IndexTail) = "."c OrElse ModClass(IndexTail) = "-"c OrElse + (ModClass(IndexTail) >= "0"c AndAlso ModClass(IndexTail) <= "9"c) OrElse + (ModClass(IndexTail) >= "a"c AndAlso ModClass(IndexTail) <= "z"c) + IndexTail += 1 + IncreaseCount += 1 + If IncreaseCount > 50 Then GoTo VersionFindFail + Loop + _Version = ModClass.Substring(IndexHead, IndexTail - IndexHead) + End If +VersionFindFail: + '获取 Dependencies + IndexHead = ModClass.IndexOf("dependencies") + If IndexHead > 0 Then + If ModClass.Count >= IndexHead + 300 Then ModClass = ModClass.Substring(IndexHead, 299) + Dim Deps As List(Of String) = RegexSearch(ModClass, "(?<=required-((before|after|before-client|after-client)?):)[0-9a-z]+(@[\(\[]{1}[0-9.,]+[\)\]]{1})?") + For Each Token As String In Deps + If Not String.IsNullOrEmpty(Token) Then + 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, "Mod Class 信息不可用(" & If(Path, "null") & ")", LogLevel.Normal) + End Try + IsInfoWithClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing + End Sub + +#End Region + + ''' + ''' 是否可能为前置 Mod。 + ''' + Public Function IsPresetMod() As Boolean + Return Dependencies.Count = 0 AndAlso (Name IsNot Nothing) AndAlso (Name.ToLower.Contains("core") OrElse Name.ToLower.Contains("lib")) + End Function + + ''' + ''' 根据完整文件路径的文件扩展名判断是否为 Mod 文件。 + ''' + Public Shared Function IsModFile(Path As String) + If Path Is Nothing OrElse Not Path.Contains(".") Then Return False + Path = Path.ToLower + If Path.EndsWith(".jar") OrElse Path.EndsWith(".zip") OrElse Path.EndsWith(".litemod") OrElse + Path.EndsWith(".jar.disabled") OrElse Path.EndsWith(".zip.disabled") OrElse Path.EndsWith(".litemod.disabled") Then Return True + Return False + End Function + + End Class + + '加载 Mod 列表 + 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) + + '获取 Mod 文件夹下的可用文件列表 + Dim ModFileList As New List(Of FileInfo) + If Directory.Exists(Loader.Input) Then + Dim RawName As String = Loader.Input.ToLower + 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 + Continue For + End If + End If + If McMod.IsModFile(File.FullName) Then ModFileList.Add(File) + Next + End If + + '确定是否显示进度 + Loader.Progress = 0.03 + If ModFileList.Count > 100 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True) + + '加载为 Mod 列表 + Dim ModList As New List(Of McMod) + For Each ModFile As FileInfo In ModFileList + Dim ModEntity As New McMod(ModFile.FullName) + ModEntity.Load() + ModList.Add(ModEntity) + Loader.Progress += 0.9 / ModFileList.Count + Next + Loader.Progress = 0.93 + + '排序 + ModList = Sort(ModList, Function(Left As McMod, Right As McMod) As Boolean + If (Left.State = McMod.McModState.Unavaliable) <> (Right.State = McMod.McModState.Unavaliable) Then + Return Left.State = McMod.McModState.Unavaliable + Else + Return Not Right.FileName.CompareTo(Left.FileName) + End If + End Function) + Loader.Progress = 0.98 + + '回设 + If Loader.IsAborted Then Exit Sub + Loader.Output = ModList + + Catch ex As Exception + Log(ex, "Mod 列表加载失败", LogLevel.Debug) + Throw + End Try + End Sub + +#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.StartsWith("[") + Dim ReqVersionTailCanEqual As Boolean = ReqVersion.EndsWith("]") + 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.StartsWith("1.") AndAlso ReqVersionHead.Contains("-") Then ReqVersionHead = ReqVersionHead.Substring(ReqVersionHead.LastIndexOf("-") + 1) + If ReqVersionTail.StartsWith("1.") AndAlso ReqVersionTail.Contains("-") Then ReqVersionTail = ReqVersionTail.Substring(ReqVersionTail.LastIndexOf("-") + 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.StartsWith("1.") AndAlso CurrentVersion.Contains("-") Then CurrentVersion = CurrentVersion.Substring(CurrentVersion.LastIndexOf("-") + 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 时出错:" & GetExceptionSummary(ex) & vbCrLf & " - " & ModEntity.FileName) + Log(ex, "检查 Mod 时出错") + End Try + Next + If Result.Count = 0 Then + Log("[Minecraft] Mod 检查未发现异常") + Else + Log("[Minecraft] Mod 检查异常结果:" & vbCrLf & Join(Result, vbCrLf)) + End If + Return Result + End Function +#End If + +End Module diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb index 6d9ae58..aeefbe9 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb @@ -254,17 +254,21 @@ Retry: For Each ModJson In Task.Input '跳过重复的 Mod(疑似 CurseForge Bug) If FileList.ContainsKey(ModJson("id").ToObject(Of Integer)) Then Continue For - '根据 modules 判断是资源包还是 Mod + '建立 CompFile + Dim File As New CompFile(ModJson, CompType.Mod) + If Not File.Available Then Continue For + '根据 modules 和文件名后缀判断是资源包还是 Mod Dim IsResourcePack As Boolean If ModJson("modules").Any Then 'modules 可能返回 null(#1006) Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList - IsResourcePack = Not ModuleNames.Contains("META-INF") AndAlso ModuleNames.Contains("pack.mcmeta") + IsResourcePack = + (Not ModuleNames.Contains("META-INF")) AndAlso (Not ModuleNames.Contains("mcmod.info")) AndAlso '不包含 META-INF 或 mcmod.info + ModuleNames.Contains("pack.mcmeta") AndAlso '包含 pack.mcmeta + (Not File.FileName.EndsWith(".jar")) '文件后缀不是 .jar Else IsResourcePack = False End If '实际的添加 - Dim File As New CompFile(ModJson, CompType.Mod) - If Not File.Available Then Continue For FileList.Add(ModJson("id"), File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{If(IsResourcePack, "resourcepacks", "mods")}\")) Task.Progress += 1 / (1 + ModFileList.Count) @@ -278,6 +282,7 @@ Retry: '构造加载器 Dim Request As New McInstallRequest With { .TargetVersionName = VersionName, + .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", .MinecraftName = Json("minecraft")("version").ToString, .ForgeVersion = ForgeVersion, .FabricVersion = FabricVersion @@ -333,7 +338,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(PathMcFolder & "versions\" & VersionName & "\") + Loader.Start(Request.TargetVersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() If ShowRibble Then FrmMain.BtnExtraDownload.Ribble() @@ -431,6 +436,7 @@ Retry: '构造加载器 Dim Request As New McInstallRequest With { .TargetVersionName = VersionName, + .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", .MinecraftName = MinecraftVersion, .ForgeVersion = ForgeVersion, .FabricVersion = FabricVersion @@ -486,7 +492,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(PathMcFolder & "versions\" & VersionName & "\") + Loader.Start(Request.TargetVersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() If ShowRibble Then FrmMain.BtnExtraDownload.Ribble() @@ -533,6 +539,7 @@ Retry: End If Dim Request As New McInstallRequest With { .TargetVersionName = VersionName, + .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", .MinecraftName = Json("gameVersion").ToString } Dim InstallExpectTime As Double = 0 @@ -584,7 +591,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} 'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件” - Loader.Start(PathMcFolder & "versions\" & VersionName & "\") + Loader.Start(Request.TargetVersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() FrmMain.BtnExtraDownload.Ribble() @@ -629,7 +636,7 @@ Retry: Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical) Exit Sub End If - Dim Request As New McInstallRequest With {.TargetVersionName = VersionName} + Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"} For Each Component In PackJson("components") Select Case If(Component("uid"), "").ToString Case "org.lwjgl" @@ -679,7 +686,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(PathMcFolder & "versions\" & VersionName & "\") + Loader.Start(Request.TargetVersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() FrmMain.BtnExtraDownload.Ribble() @@ -738,6 +745,7 @@ Retry: End If Dim Request As New McInstallRequest With { .TargetVersionName = VersionName, + .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", .MinecraftName = Addons("game"), .OptiFineVersion = If(Addons.ContainsKey("optifine"), Addons("optifine"), Nothing), .ForgeVersion = If(Addons.ContainsKey("forge"), Addons("forge"), Nothing), @@ -777,7 +785,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} 'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件” - Loader.Start(PathMcFolder & "versions\" & VersionName & "\") + Loader.Start(Request.TargetVersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() FrmMain.BtnExtraDownload.Ribble() diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb index 7543692..5059754 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb @@ -116,7 +116,7 @@ Get Return _State End Get - Set(ByVal value As MinecraftState) + Set(value As MinecraftState) If _State = value Then Exit Property _State = value WatcherStateChanged() diff --git a/Plain Craft Launcher 2/Modules/ModEvent.vb b/Plain Craft Launcher 2/Modules/ModEvent.vb index dea939e..51942e9 100644 --- a/Plain Craft Launcher 2/Modules/ModEvent.vb +++ b/Plain Craft Launcher 2/Modules/ModEvent.vb @@ -13,11 +13,11 @@ Case "打开网页" Data(0) = Data(0).Replace("\", "/") - If Not (Data(0).StartsWith("http://") OrElse Data(0).StartsWith("https://")) Then - MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败") + If Not Data(0).Contains("://") OrElse Data(0).ToLower.StartsWith("file") Then '为了支持更多协议(#2200) + MyMsgBox("EventData 必须为一个网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败") Exit Sub End If - Hint("正在打开网页,请稍候……") + Hint("正在开启中,请稍候……") OpenWebsite(Data(0)) Case "打开文件", "打开帮助" @@ -65,15 +65,21 @@ RunInThread(Sub() PageOtherTest.MemoryOptimize(True)) Case "清理垃圾" - PageOtherTest.RubbishClear() + RunInThread(Sub() PageOtherTest.RubbishClear()) Case "弹出窗口" MyMsgBox(Data(1).Replace("\n", vbCrLf), Data(0).Replace("\n", vbCrLf)) + Case "切换页面" + FrmMain.PageChange(Val(Data(0)), Val(Data(1))) + + Case "导入整合包" + RunInThread(Sub() ModpackInstall()) + Case "下载文件" Data(0) = Data(0).Replace("\", "/") If Not (Data(0).StartsWith("http://") OrElse Data(0).StartsWith("https://")) Then - MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的协议。", "事件执行失败") + MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败") Exit Sub End If PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0))) diff --git a/Plain Craft Launcher 2/Modules/ModMusic.vb b/Plain Craft Launcher 2/Modules/ModMusic.vb index 1a054a7..f973d7f 100644 --- a/Plain Craft Launcher 2/Modules/ModMusic.vb +++ b/Plain Craft Launcher 2/Modules/ModMusic.vb @@ -25,7 +25,7 @@ For Each File In My.Computer.FileSystem.GetFiles(Path & "PCL\Musics\", FileIO.SearchOption.SearchAllSubDirectories, "*.*") '文件夹可能会被加入 .ini 文件夹配置文件、一些乱七八糟的 .jpg 文件啥的 Dim Extend As String = File.Split(".").Last.ToLower - If Not (Extend = "ini" OrElse Extend = "jpg" OrElse Extend = "txt" OrElse Extend = "cfg" OrElse Extend = "png") Then + If Not (Extend = "ini" OrElse Extend = "jpg" OrElse Extend = "txt" OrElse Extend = "cfg" OrElse Extend = "lrc" OrElse Extend = "png") Then MusicAllList.Add(File) End If Next @@ -277,7 +277,7 @@ Log(ex, "播放音乐失败(" & MusicCurrent & ")", LogLevel.Developer) If MusicAllList.Count > 1 Then Thread.Sleep(1000) - MusicStartPlay(DequeueNextMusicAddress) + MusicStartPlay(DequeueNextMusicAddress, IsFirstLoad) End If Finally If CurrentWave IsNot Nothing Then CurrentWave.Dispose() diff --git a/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb b/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb index 17ea092..1f5ea45 100644 --- a/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb +++ b/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb @@ -25,7 +25,7 @@ Public Class CookieWebClient ''' Public Timeout As Integer = 600000 - Protected Overrides Function GetWebRequest(ByVal address As Uri) As WebRequest + Protected Overrides Function GetWebRequest(address As Uri) As WebRequest Dim r As WebRequest = MyBase.GetWebRequest(address) Dim request = TryCast(r, HttpWebRequest) @@ -37,19 +37,19 @@ Public Class CookieWebClient Return r End Function - Protected Overrides Function GetWebResponse(ByVal request As WebRequest, ByVal result As IAsyncResult) As WebResponse + Protected Overrides Function GetWebResponse(request As WebRequest, result As IAsyncResult) As WebResponse Dim response As WebResponse = MyBase.GetWebResponse(request, result) ReadCookies(response) Return response End Function - Protected Overrides Function GetWebResponse(ByVal request As WebRequest) As WebResponse + Protected Overrides Function GetWebResponse(request As WebRequest) As WebResponse Dim response As WebResponse = MyBase.GetWebResponse(request) ReadCookies(response) Return response End Function - Private Sub ReadCookies(ByVal r As WebResponse) + Private Sub ReadCookies(r As WebResponse) Dim response = TryCast(r, HttpWebResponse) If response IsNot Nothing Then Dim cookies As CookieCollection = response.Cookies diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index 861ab2e..3e3c0d8 100644 --- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb +++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb @@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 ' 方法是按如下所示使用“*” - - + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index 8730346..6b50e8f 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -386,7 +386,7 @@ Public Module ModDownloadLib '选择 Java Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0)) If Java Is Nothing Then - If Not JavaDownloadConfirm(17) Then Throw New Exception("由于未找到 Java,已取消安装。") + If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。") '开始自动下载 Dim JavaLoader = JavaFixLoaders(17) JavaLoader.Start(17, IsForceRestart:=True) @@ -489,7 +489,7 @@ Public Module ModDownloadLib ''' ''' 获取下载某个 OptiFine 版本的加载器列表。 ''' - Private Function McDownloadOptiFineLoader(DownloadInfo As DlOptiFineListEntry, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase) + 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) @@ -553,14 +553,14 @@ Public Module ModDownloadLib '拷贝原版文件 If Not IsCustomFolder Then Exit Sub SyncLock VanillaSyncLock - Dim ClientName As String = New DirectoryInfo(ClientDownloadLoader.Input).Name '#2031 报告了此处 ClientDownloadLoader.Input 为 null 的问题,但无法复现 + Dim ClientName As String = GetFolderNameFromPath(ClientFolder) Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit) If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") Then - CopyFile(ClientDownloadLoader.Input & ClientName & ".json", + CopyFile(ClientFolder & ClientName & ".json", McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") End If If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") Then - CopyFile(ClientDownloadLoader.Input & ClientName & ".jar", + CopyFile(ClientFolder & ClientName & ".jar", McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") End If End SyncLock @@ -1114,7 +1114,7 @@ Public Module ModDownloadLib '选择 Java Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0)) If Java Is Nothing Then - If Not JavaDownloadConfirm(17) Then Throw New Exception("由于未找到 Java,已取消安装。") + If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。") '开始自动下载 Dim JavaLoader = JavaFixLoaders(17) JavaLoader.Start(17, IsForceRestart:=True) @@ -1259,7 +1259,7 @@ Public Module ModDownloadLib ''' ''' 获取下载某个 Forge 版本的加载器列表。 ''' - Private Function McDownloadForgeLoader(Version As String, Inherit As String, Optional DownloadInfo As DlForgeVersionEntry = Nothing, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase) + Private Function McDownloadForgeLoader(Version As String, Inherit As String, Optional DownloadInfo As DlForgeVersionEntry = Nothing, 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) @@ -1386,13 +1386,13 @@ Public Module ModDownloadLib '拷贝原版文件 If Not IsCustomFolder Then Exit Sub SyncLock VanillaSyncLock - Dim ClientName As String = New DirectoryInfo(ClientDownloadLoader.Input).Name + Dim ClientName As String = GetFolderNameFromPath(ClientFolder) Directory.CreateDirectory(McFolder & "versions\" & Inherit) If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".json") Then - CopyFile(ClientDownloadLoader.Input & ClientName & ".json", McFolder & "versions\" & Inherit & "\" & Inherit & ".json") + CopyFile(ClientFolder & ClientName & ".json", McFolder & "versions\" & Inherit & "\" & Inherit & ".json") End If If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") Then - CopyFile(ClientDownloadLoader.Input & ClientName & ".jar", McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") + CopyFile(ClientFolder & ClientName & ".jar", McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") End If End SyncLock #End Region @@ -1797,6 +1797,10 @@ Public Module ModDownloadLib ''' 必填。安装目标版本名称。 ''' Public TargetVersionName As String + ''' + ''' 必填。安装目标文件夹。 + ''' + Public TargetVersionFolder As String ''' ''' 必填。欲下载的 Minecraft 的版本名。 @@ -1909,7 +1913,7 @@ Public Module ModDownloadLib Dim Loader As New LoaderCombo(Of String)(Request.TargetVersionName & " 安装", SubLoaders) With {.OnStateChanged = AddressOf McInstallState} '启动 - Loader.Start(PathMcFolder & "versions\" & Request.TargetVersionName & "\") + Loader.Start(Request.TargetVersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() FrmMain.BtnExtraDownload.Ribble() @@ -2009,28 +2013,29 @@ Public Module ModDownloadLib 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, .Block = Request.ForgeVersion 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, False)) With {.Show = False, .ProgressWeight = 24, .Block = Request.ForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) + LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 24, .Block = Request.ForgeVersion 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, McDownloadForgeLoader(Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 25, .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) + LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgeLoader(Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 25, .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) End If 'LiteLoader If Request.LiteLoaderEntry IsNot Nothing Then LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.MinecraftName, McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 1, .Block = Request.FabricVersion Is Nothing}) End If - 'Fabric Loader + 'Fabric If Request.FabricVersion IsNot Nothing Then LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & Request.FabricVersion, McDownloadFabricLoader(Request.FabricVersion, Request.MinecraftName, TempMcFolder, False)) With {.Show = False, .ProgressWeight = 2, .Block = True}) End If '合并安装 - LoaderList.Add(New LoaderTask(Of String, String)("安装游戏", Sub(Task As LoaderTask(Of String, String)) - InstallMerge(OutputFolder, OutputFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, FabricFolder, LiteLoaderFolder) - Task.Progress = 0.3 - If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries") - If Directory.Exists(TempMcFolder & "mods") Then CopyDirectory(TempMcFolder & "mods", PathMcFolder & "mods") - End Sub) With {.ProgressWeight = 2, .Block = True}) + LoaderList.Add(New LoaderTask(Of String, String)("安装游戏", + Sub(Task As LoaderTask(Of String, String)) + InstallMerge(OutputFolder, OutputFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, FabricFolder, LiteLoaderFolder) + Task.Progress = 0.3 + If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries") + If Directory.Exists(TempMcFolder & "mods") Then CopyDirectory(TempMcFolder & "mods", PathMcFolder & "mods") + End Sub) With {.ProgressWeight = 2, .Block = True}) '补全文件 If Not DontFixLibraries AndAlso (Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.Split(".")(0) >= 20) OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing) Then diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb index d4034a4..01e94ba 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb @@ -945,11 +945,16 @@ ''' 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。 ''' Private Function LoadFabricApiGetError() As String - If SelectedFabric Is Nothing Then Return "需要安装 Fabric" If 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 "正在获取版本列表……" + End If For Each Version In DlFabricApiLoader.Output - If IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then Return Nothing + If Not IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then Continue For + If SelectedFabric Is Nothing Then Return "需要安装 Fabric" + Return Nothing Next Return "没有可用版本" End Function @@ -1032,14 +1037,21 @@ ''' 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。 ''' Private Function LoadOptiFabricGetError() As String - 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" If SelectedMinecraftId.StartsWith("1.14") OrElse SelectedMinecraftId.StartsWith("1.15") Then Return "不兼容老版本 Fabric,请手动下载 OptiFabric Origins" If LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……" If LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadOptiFabric.State, Object).Error.Message + 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 "正在获取版本列表……" + End If For Each Version In DlOptiFabricLoader.Output - If IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Return Nothing + If Not IsSuitableOptiFabric(Version, SelectedMinecraftId) 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 "没有可用版本" End Function @@ -1114,6 +1126,7 @@ '提交安装申请 Dim Request As New McInstallRequest With { .TargetVersionName = TextSelectName.Text, + .TargetVersionFolder = $"{PathMcFolder}versions\{TextSelectName.Text}\", .MinecraftJson = SelectedMinecraftJsonUrl, .MinecraftName = SelectedMinecraftId, .OptiFineEntry = SelectedOptiFine, diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb index 6cbaffa..5f1849a 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb @@ -5,7 +5,7 @@ '皮肤储存 Private _Address As String - Public Property Address() As String + Public Property Address As String Get Return _Address End Get @@ -76,12 +76,19 @@ ''' Public Sub Load() Try - Address = Loader.Output '检查文件存在 - If Address = "" Then Throw New Exception("皮肤加载器 " & Loader.Name & " 没有输出") + Address = Loader.Output + If String.IsNullOrEmpty(Address) Then Throw New Exception("皮肤加载器 " & Loader.Name & " 没有输出") If Not Address.StartsWith(PathImage) AndAlso Not File.Exists(Address) Then Throw New FileNotFoundException("皮肤文件未找到", Address) '加载 - Dim Image As New MyBitmap(Address) + Dim Image As MyBitmap + Try + Image = New MyBitmap(Address) + Catch ex As Exception '#2272 + Log(ex, $"皮肤文件已损坏:{Address}", LogLevel.Hint) + File.Delete(Address) + Exit Sub + End Try ImgBack.Tag = Address '大小检查 Dim Scale As Integer = Image.Pic.Width / 64 diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb index ed51f71..eec5ebe 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb @@ -17,6 +17,7 @@ Private IsLoad As Boolean = False Private Sub OnLoaded() Handles Me.Loaded + FormMain.EndProgramForce(Result.Aborted) If IsLoad Then Exit Sub IsLoad = True '启动监视线程 diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb index 282319f..85ee5c3 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb @@ -11,6 +11,7 @@ Public Class PageLinkIoi '页面初始化 Private IsLoad As Boolean = False Private Sub MeLoaded() Handles Me.Loaded + FormMain.EndProgramForce(Result.Aborted) '重复加载部分 PanBack.ScrollToHome() diff --git a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb index f5b8103..b3874fe 100644 --- a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb @@ -187,6 +187,7 @@ End Try FrmSpeedRight.PanMain.Children.Insert(0, Card) RightCards.Add(Loader.Name, Card) + Log($"[Watcher] 新建下载管理卡片:{Loader.Name}") '添加取消按钮 Dim Cancel As New MyIconButton With {.Name = "BtnCancel", .Logo = "F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z", .Height = 20, .Margin = New Thickness(0, 10, 10, 0), .LogoScale = 1.1, .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Top} Card.Children.Add(Cancel) @@ -194,6 +195,8 @@ AniDispose(sender, False) AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then FrmMain.PageBack()) RightCards.Remove(Loader.Name) + LoaderTaskbar.Remove(Loader) + Log($"[Watcher] 关闭下载管理卡片:{Loader.Name}") RunInThread(Sub() Loader.Abort()) End Sub '如果已经失败,再刷新一次,修改成失败的控件 @@ -217,6 +220,7 @@ Dim Card As Grid = RightCards(Loader.Name) FrmSpeedRight.PanMain.Children.Remove(Card) RightCards.Remove(Loader.Name) + Log($"[Watcher] 移除下载管理卡片:{Loader.Name}") End Sub) End If End Sub diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml index 33bc4e1..2df274d 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml @@ -7,22 +7,20 @@ - + - + - - - - + + @@ -47,5 +45,22 @@ + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb index 99667ca..bbd3318 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb @@ -1,4 +1,7 @@ -Public Class PageVersionMod +Imports System.Security.Principal +Imports PCL.MyLoading + +Public Class PageVersionMod #Region "初始化" @@ -6,7 +9,11 @@ Public Sub PageOther_Loaded() Handles Me.Loaded PanBack.ScrollToHome() + AniControlEnabled += 1 + SelectedMods.Clear() RefreshList() + ChangeAllSelected(False) + AniControlEnabled -= 1 '非重复加载部分 If IsLoad Then Exit Sub @@ -42,6 +49,7 @@ #Region "UI 化" + Private PanItems As StackPanel ''' ''' 将 Mod 列表加载为 UI。 ''' @@ -62,13 +70,13 @@ SearchBox.Text = "" '建立 StackPanel - Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, If(List.Count > 0, 20, 0)), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)} + PanItems = New StackPanel With {.Margin = New Thickness(18, MyCard.SwapedHeight, 18, If(List.Count > 0, 20, 0)), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)} For Each ModEntity As McMod In List - NewStack.Children.Add(McModListItem(ModEntity)) + PanItems.Children.Add(McModListItem(ModEntity)) Next '建立 MyCard Dim NewCard As New MyCard With {.Title = McModGetTitle(List), .Margin = New Thickness(0, 0, 0, 15)} - NewCard.Children.Add(NewStack) + NewCard.Children.Add(PanItems) PanList.Children.Add(NewCard) '显示提示 If List.Count > 0 AndAlso Not Setup.Get("HintModDisable") Then @@ -88,20 +96,12 @@ For Each ModEntity As McMod In List Counter(ModEntity.State) += 1 Next - If List.Count = 0 Then - '全空 - Return "未找到任何 Mod" - ElseIf Counter(McMod.McModState.Disabled) = 0 AndAlso Counter(McMod.McModState.Unavaliable) = 0 Then - '只有启用的 Mod - Return "Mod 列表(" & Counter(McMod.McModState.Fine) & ")" - Else - '混合种类 - Dim TypeList As New List(Of String) - If Counter(McMod.McModState.Fine) > 0 Then TypeList.Add("启用 " & Counter(McMod.McModState.Fine)) - If Counter(McMod.McModState.Disabled) > 0 Then TypeList.Add("禁用 " & Counter(McMod.McModState.Disabled)) - If Counter(McMod.McModState.Unavaliable) > 0 Then TypeList.Add("错误 " & Counter(McMod.McModState.Unavaliable)) - Return "Mod 列表(" & Join(TypeList, ",") & ")" - End If + If List.Count = 0 Then Return "未找到任何 Mod" + Dim TypeList As New List(Of String) + If Counter(McMod.McModState.Fine) > 0 Then TypeList.Add("启用 " & Counter(McMod.McModState.Fine)) + If Counter(McMod.McModState.Disabled) > 0 Then TypeList.Add("禁用 " & Counter(McMod.McModState.Disabled)) + If Counter(McMod.McModState.Unavaliable) > 0 Then TypeList.Add("错误 " & Counter(McMod.McModState.Unavaliable)) + Return "Mod 列表(" & Join(TypeList, ",") & ")" End Function Private Function McModListItem(Entry As McMod) As MyListItem '图标 @@ -138,16 +138,21 @@ ' Desc += " : " & Join(DepList, ", ") 'End If '实例化 - Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Title, .Info = Desc, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry} - NewItem.PaddingRight = If(Entry.IsFileAvailable, 73, 55) - '事件 - NewItem.ContentHandler = AddressOf McModContent + AniControlEnabled += 1 + Dim NewItem As New MyListItem With { + .LogoClickable = True, .Logo = Logo, .SnapsToDevicePixels = True, .Title = Title, .Info = Desc, .Height = 42, .Tag = Entry, + .Type = MyListItem.CheckType.CheckBox, .IsScaleAnimationEnabled = False, + .PaddingRight = 73, + .ContentHandler = AddressOf McModContent, .Checked = SelectedMods.Contains(Entry.RawFileName) + } + AniControlEnabled -= 1 Return NewItem End Function Private Sub McModContent(sender As MyListItem, e As EventArgs) Dim ModEntity As McMod = sender.Tag '注册点击事件 - AddHandler sender.Click, AddressOf Item_Click + AddHandler sender.Changed, AddressOf CheckChanged + AddHandler sender.LogoClick, AddressOf ItemLogo_Click '图标按钮 Dim BtnDel As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonDelete, .Tag = sender} BtnDel.ToolTip = "删除" @@ -161,18 +166,14 @@ ToolTipService.SetVerticalOffset(BtnOpen, 30) ToolTipService.SetHorizontalOffset(BtnOpen, 2) AddHandler BtnOpen.Click, AddressOf Open_Click - If ModEntity.IsFileAvailable Then - Dim BtnCont As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .Tag = sender} - BtnCont.ToolTip = "详情" - ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center) - ToolTipService.SetVerticalOffset(BtnCont, 30) - ToolTipService.SetHorizontalOffset(BtnCont, 2) - AddHandler BtnCont.Click, AddressOf Info_Click - AddHandler sender.MouseRightButtonDown, AddressOf Info_Click - sender.Buttons = {BtnCont, BtnOpen, BtnDel} - Else - sender.Buttons = {BtnOpen, BtnDel} - End If + Dim BtnCont As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .Tag = sender} + BtnCont.ToolTip = "详情" + ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center) + ToolTipService.SetVerticalOffset(BtnCont, 30) + ToolTipService.SetHorizontalOffset(BtnCont, 2) + AddHandler BtnCont.Click, AddressOf Info_Click + AddHandler sender.MouseRightButtonDown, AddressOf Info_Click + sender.Buttons = {BtnCont, BtnOpen, BtnDel} End Sub #End Region @@ -210,13 +211,144 @@ #End If ''' - ''' 一键启用 / 禁用。 + ''' 全选。 ''' - Private Sub BtnManageChange_Click(sender As MyButton, e As EventArgs) Handles BtnManageEnabled.Click, BtnManageDisabled.Click + Private Sub BtnManageSelectAll_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageSelectAll.Click + ChangeAllSelected(True) + End Sub + +#End Region + +#Region "选择" + + '选择的 Mod 的路径(不含 .disabled) + Public SelectedMods As New List(Of String) + + '单项切换选择状态 + Public Sub CheckChanged(sender As MyListItem, e As RouteEventArgs) + If AniControlEnabled <> 0 Then Return + '更新选择了的内容 + Dim SelectedKey As String = CType(sender.Tag, McMod).RawFileName + If sender.Checked Then + If Not SelectedMods.Contains(SelectedKey) Then SelectedMods.Add(SelectedKey) + Else + SelectedMods.Remove(SelectedKey) + End If + '更新下边栏 UI + RefreshBottomBar() + End Sub + + '改变下边栏状态 + Private ShownCount As Integer = 0 + Private Sub RefreshBottomBar() + '计数 + Dim NewCount As Integer = SelectedMods.Count + Dim Selected = NewCount > 0 + If Selected Then LabSelect.Text = $"已选择 {NewCount} 个文件" '取消所有选择时不更新数字 + '按钮可用性 + If Selected Then + Dim HasEnabled As Boolean = False + Dim HasDisabled As Boolean = False + For Each ModEntity In McModLoader.Output + If SelectedMods.Contains(ModEntity.RawFileName) Then + If ModEntity.State = McMod.McModState.Fine Then + HasEnabled = True + ElseIf ModEntity.State = McMod.McModState.Disabled Then + HasDisabled = True + End If + End If + Next + BtnSelectDisable.IsEnabled = HasEnabled + BtnSelectEnable.IsEnabled = HasDisabled + End If + '更新显示状态 + CardSelect.IsHitTestVisible = Selected + If AniControlEnabled = 0 Then + If Selected Then + '仅在数量增加时播放出现/跳跃动画 + If ShownCount >= NewCount Then + ShownCount = NewCount + Return + Else + ShownCount = NewCount + End If + '出现/跳跃动画 + CardSelect.Visibility = Visibility.Visible + AniStart({ + AaOpacity(CardSelect, 1 - CardSelect.Opacity, 60), + AaTranslateY(CardSelect, -27 - TransSelect.Y, 120, Ease:=New AniEaseOutFluent(AniEasePower.Weak)), + AaTranslateY(CardSelect, 3, 150, 120, Ease:=New AniEaseInoutFluent(AniEasePower.Weak)), + AaTranslateY(CardSelect, -1, 90, 270, Ease:=New AniEaseInoutFluent(AniEasePower.Weak)) + }, "Mod Sidebar") + Else + '不重复播放隐藏动画 + If ShownCount = 0 Then Return + ShownCount = 0 + '隐藏动画 + AniStart({ + AaOpacity(CardSelect, -CardSelect.Opacity, 90), + AaTranslateY(CardSelect, -10 - TransSelect.Y, 90, Ease:=New AniEaseInFluent(AniEasePower.Weak)), + AaCode(Sub() CardSelect.Visibility = Visibility.Collapsed, After:=True) + }, "Mod Sidebar") + End If + Else + AniStop("Mod Sidebar") + ShownCount = NewCount + If Selected Then + CardSelect.Visibility = Visibility.Visible + CardSelect.Opacity = 1 + TransSelect.Y = -25 + Else + CardSelect.Visibility = Visibility.Collapsed + CardSelect.Opacity = 0 + TransSelect.Y = -10 + End If + End If + End Sub + + '切换所有项的选择状态 + Private Sub ChangeAllSelected(Value As Boolean) + AniControlEnabled += 1 + SelectedMods.Clear() + If IsSearching Then + '搜索中 + For Each Item As MyListItem In PanSearchList.Children + Item.Checked = Value + If Value Then SelectedMods.Add(CType(Item.Tag, McMod).RawFileName) + Next + If Not Value Then '只取消选择 + If PanItems IsNot Nothing Then + For Each Item As MyListItem In PanItems.Children + Item.Checked = Value + Next + End If + End If + Else + '非搜索中 + If PanItems IsNot Nothing Then + For Each Item As MyListItem In PanItems.Children + Item.Checked = Value + If Value Then SelectedMods.Add(CType(Item.Tag, McMod).RawFileName) + Next + End If + End If + AniControlEnabled -= 1 + '更新下边栏 UI + RefreshBottomBar() + End Sub + Private Sub Load_State(sender As Object, newState As MyLoadingState, oldState As MyLoadingState) Handles Load.StateChanged + ChangeAllSelected(False) + End Sub + +#End Region + +#Region "下边栏" + + '启用 / 禁用 + Private Sub BtnSelectEorD_Click(sender As MyIconTextButton, e As RouteEventArgs) Handles BtnSelectEnable.Click, BtnSelectDisable.Click Dim IsSuccessful As Boolean = True - Dim SuccessfulCount As Integer = 0 - Dim IsDisable As Boolean = sender.Text.Contains("禁用") - For Each ModEntity In McModLoader.Output + Dim IsDisable As Boolean = sender.Equals(BtnSelectDisable) + For Each ModEntity In McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)).ToList() Dim NewPath As String = Nothing If ModEntity.State = McMod.McModState.Fine And IsDisable Then '禁用 @@ -225,7 +357,6 @@ '启用 NewPath = ModEntity.Path.Substring(0, ModEntity.Path.Count - ".disabled".Count) Else - SuccessfulCount += 1 Continue For End If '重命名 @@ -233,27 +364,99 @@ If File.Exists(NewPath) AndAlso Not File.Exists(ModEntity.Path) Then Continue For '因为未知原因 Mod 的状态已经切换完了 File.Delete(NewPath) FileSystem.Rename(ModEntity.Path, NewPath) - SuccessfulCount += 1 Catch ex As FileNotFoundException - Log(ex, "未找到需要重命名的 Mod(" & If(ex.FileName, "null") & ")", LogLevel.Feedback) - RefreshList() - Exit Sub + Log(ex, $"未找到需要重命名的 Mod({If(ModEntity.Path, "null")})", LogLevel.Feedback) + RefreshList(True) + Return Catch ex As Exception - Log(ex, "全局状态改变中重命名 Mod 失败") + Log(ex, $"批量重命名 Mod 失败({If(ModEntity.Path, "null")})") IsSuccessful = False End Try + '更改 Loader 和 UI 中的列表 + Dim NewModEntity As New McMod(NewPath) + Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity) + McModLoader.Output.RemoveAt(IndexOfLoader) + McModLoader.Output.Insert(IndexOfLoader, NewModEntity) + Dim Parent As StackPanel = If(IsSearching, PanSearchList, PanItems) + Dim IndexOfUi As Integer = Parent.Children.IndexOf(Parent.Children.OfType(Of MyListItem).First(Function(i) CType(i.Tag, McMod) Is ModEntity)) + Parent.Children.RemoveAt(IndexOfUi) + Parent.Children.Insert(IndexOfUi, McModListItem(NewModEntity)) Next - If Not IsSuccessful Then Hint("由于文件被占用,部分 Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical) - Log("[System] 已确认 " & SuccessfulCount & "/" & McModLoader.Output.Count & " 个 Mod 的状态与要求一致") - RefreshList() + If Not IsSearching Then + '改变禁用数量的显示 + CType(PanItems.Parent, MyCard).Title = McModGetTitle(McModLoader.Output) + '更新加载器状态 + LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly) + End If + If Not IsSuccessful Then + Hint("由于文件被占用,部分 Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical) + RefreshList(True) + Else + ChangeAllSelected(False) + End If + End Sub + + '删除 + Private Sub BtnSelectDelete_Click() Handles BtnSelectDelete.Click + Dim IsSuccessful As Boolean = True + Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown + Dim DeleteList As List(Of McMod) = McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)).ToList() + For Each ModEntity In DeleteList + '删除 + Try + If IsShiftPressed Then + File.Delete(ModEntity.Path) + Else + My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) + End If + Catch ex As OperationCanceledException + Log(ex, "批量删除 Mod 被主动取消") + RefreshList(True) + Return + Catch ex As Exception + Log(ex, $"批量删除 Mod 失败({ModEntity.Path})", LogLevel.Msgbox) + IsSuccessful = False + End Try + '更改 Loader 和 UI 中的列表 + McModLoader.Output.Remove(ModEntity) + Dim Parent As StackPanel = If(IsSearching, PanSearchList, PanItems) + Dim IndexOfUi As Integer = Parent.Children.IndexOf(Parent.Children.OfType(Of MyListItem).First(Function(i) CType(i.Tag, McMod) Is ModEntity)) + Parent.Children.RemoveAt(IndexOfUi) + Next + If Not IsSearching Then + '改变禁用数量的显示 + CType(PanItems.Parent, MyCard).Title = McModGetTitle(McModLoader.Output) + '更新加载器状态 + LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly) + End If + If Not IsSuccessful Then + Hint("由于文件被占用,部分 Mod 删除失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical) + RefreshList(True) + ElseIf If(IsSearching, PanSearchList, PanItems).Children.Count = 0 Then + RefreshList(True) + Else + ChangeAllSelected(False) + End If + If IsSuccessful Then + If IsShiftPressed Then + Hint($"已彻底删除 {DeleteList.Count} 个文件!", HintType.Finish) + Else + Hint($"已将 {DeleteList.Count} 个文件删除到回收站!", HintType.Finish) + End If + End If + End Sub + + '取消选择 + Private Sub BtnSelectCancel_Click() Handles BtnSelectCancel.Click + ChangeAllSelected(False) End Sub #End Region #Region "单个 Mod 项" - '点击:切换可用状态 / 显示错误原因 - Public Sub Item_Click(sender As MyListItem, e As EventArgs) + '点击 Logo:切换可用状态 / 显示错误原因 + Public Sub ItemLogo_Click(sender As MyListItem, e As EventArgs) Try Dim ModEntity As McMod = sender.Tag @@ -291,12 +494,13 @@ Parent.Children.RemoveAt(IndexOfUi) Parent.Children.Insert(IndexOfUi, McModListItem(NewModEntity)) '仅在非搜索页面才执行,以确保搜索页内外显示一致 - If String.IsNullOrWhiteSpace(SearchBox.Text) Then + If Not IsSearching Then '改变禁用数量的显示 CType(Parent.Parent, MyCard).Title = McModGetTitle(McModLoader.Output) '更新加载器状态 LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly) End If + RefreshBottomBar() Catch ex As Exception Log(ex, "单个状态改变中重命名 Mod 失败") @@ -358,27 +562,30 @@ Dim ListItem As MyListItem ListItem = If(TypeOf sender Is MyIconButton, sender.Tag, sender) Dim ModEntity As McMod = ListItem.Tag + '加载失败信息 + If ModEntity.State = McMod.McModState.Unavaliable Then + MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntity.FileUnavailableReason), "Mod 读取失败") + Return + End If '获取信息 Dim ContentLines As New List(Of String) If ModEntity.Description IsNot Nothing Then ContentLines.Add(ModEntity.Description & vbCrLf) If ModEntity.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntity.Authors) ContentLines.Add("文件:" & ModEntity.FileName & "(" & GetString(New FileInfo(ModEntity.Path).Length) & ")") If ModEntity.Version IsNot Nothing Then ContentLines.Add("版本:" & ModEntity.Version) - If ModeDebug Then - Dim DebugInfo As New List(Of String) - If ModEntity.ModId IsNot Nothing Then - DebugInfo.Add("Mod ID:" & ModEntity.ModId) - End If - If ModEntity.Dependencies.Count > 0 Then - DebugInfo.Add("依赖于:") - For Each Dep In ModEntity.Dependencies - DebugInfo.Add(" - " & Dep.Key & If(Dep.Value Is Nothing, "", ",版本:" & Dep.Value)) - Next - End If - If DebugInfo.Count > 0 Then - ContentLines.Add(vbCrLf & "—————— 调试信息 ——————") - ContentLines.AddRange(DebugInfo) - End If + Dim DebugInfo As New List(Of String) + If ModEntity.ModId IsNot Nothing Then + DebugInfo.Add("Mod ID:" & ModEntity.ModId) + End If + If ModEntity.Dependencies.Count > 0 Then + DebugInfo.Add("依赖于:") + For Each Dep In ModEntity.Dependencies + DebugInfo.Add(" - " & Dep.Key & If(Dep.Value Is Nothing, "", ",版本:" & Dep.Value)) + Next + End If + If DebugInfo.Count > 0 Then + ContentLines.Add("") + ContentLines.AddRange(DebugInfo) End If '获取用于搜索的 Mod 名称 Dim ModOriginalName As String = ModEntity.Name.Replace(" ", "+") @@ -426,22 +633,14 @@ #End Region + Public ReadOnly Property IsSearching As Boolean + Get + Return Not String.IsNullOrWhiteSpace(SearchBox.Text) + End Get + End Property Public Sub SearchRun() Handles SearchBox.TextChanged - If String.IsNullOrWhiteSpace(SearchBox.Text) Then - '隐藏 - LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.RunOnUpdated) - AniStart({ - AaOpacity(PanSearch, -PanSearch.Opacity, 100), - AaCode(Sub() - PanSearch.Height = 0 - PanSearch.Visibility = Visibility.Collapsed - PanList.Visibility = Visibility.Visible - PanManage.Visibility = Visibility.Visible - End Sub,, True), - AaOpacity(PanList, 1 - PanList.Opacity, 150, 30), - AaOpacity(PanManage, 1 - PanManage.Opacity, 150, 30) - }, "FrmVersionMod Search Switch", True) - Else + ChangeAllSelected(False) + If IsSearching Then '构造请求 Dim QueryList As New List(Of SearchEntry(Of McMod)) For Each Entry As McMod In McModLoader.Output @@ -472,15 +671,25 @@ '显示 AniStart({ AaOpacity(PanList, -PanList.Opacity, 100), - AaOpacity(PanManage, -PanManage.Opacity, 100), AaCode(Sub() PanList.Visibility = Visibility.Collapsed - PanManage.Visibility = Visibility.Collapsed PanSearch.Visibility = Visibility.Visible PanSearch.TriggerForceResize() End Sub,, True), AaOpacity(PanSearch, 1 - PanSearch.Opacity, 200, 60) }, "FrmVersionMod Search Switch", True) + Else + '隐藏 + LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.RunOnUpdated) + AniStart({ + AaOpacity(PanSearch, -PanSearch.Opacity, 100), + AaCode(Sub() + PanSearch.Height = 0 + PanSearch.Visibility = Visibility.Collapsed + PanList.Visibility = Visibility.Visible + End Sub,, True), + AaOpacity(PanList, 1 - PanList.Opacity, 150, 30) + }, "FrmVersionMod Search Switch", True) End If End Sub diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml index ac8ad0f..2209d92 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml @@ -7,8 +7,8 @@ d:DesignWidth="800" d:DesignHeight="800"> - - + + @@ -215,4 +215,4 @@ - + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj index cd56836..e902ab1 100644 --- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj +++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj @@ -182,8 +182,12 @@ + + MyIconTextButton.xaml + + @@ -406,6 +410,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/Plain Craft Launcher 2/Resources/Custom.xaml b/Plain Craft Launcher 2/Resources/Custom.xaml index f731ba5..1dcaf68 100644 --- a/Plain Craft Launcher 2/Resources/Custom.xaml +++ b/Plain Craft Launcher 2/Resources/Custom.xaml @@ -103,26 +103,34 @@ - - + - - - + + - - - - - - + + + + diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip index 49a5e43..d2a0c1a 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 dd0efd2..36815de 100644 --- a/Plain Craft Launcher 2/Resources/ModData.txt +++ b/Plain Craft Launcher 2/Resources/ModData.txt @@ -1,6 +1,6 @@  industrial-craft|工业时代2 (Industrial Craft 2)|515771 - +@redpower2-core|红石力量2 (RedPower2)|71843 buildcraft@|建筑 (BuildCraft)|884720 forestry@|林业*|708812 railcraft|铁路*|603270 @@ -17,7 +17,7 @@ powerconverters-portablejims-fork|能源转换 (PowerConverters) modular-forcefield-system|模块化力场系统* -iron-chests|更多箱子* - Forge|372723¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)|372723¨iron-chests-fabric@|更多箱子 (Iron Chests) +iron-chests-fabric@|更多箱子 (Iron Chests)¨iron-chests|更多箱子* - Forge|372723¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)|372723 balkons-weaponmod|更多武器 (Balkon's WeaponMod) advanced-machines|高级机械 (AdvancedMachines) advanced-solar-panels|高级太阳能* @@ -110,7 +110,7 @@ biomes-o-plenty|超多生物群系 (Biomes O' Plenty)|814732 enhanced-portals-3|增强传送门3* compact-solars|压缩太阳能阵列 (CompactSolars / Compact Solar Arrays)|807551 -bibliocraft|收藏馆 (BiblioCraft) +bibliocraft|收藏馆 (BiblioCraft)|644088 natura|自然* atomic-science|原子科学* @@ -126,7 +126,7 @@ modular-powersuits|模块化动力套装* - +@immibis-microblocks|Immibis的小方块 (Immibis's Microblocks) @@ -156,12 +156,12 @@ minechess|国际象棋 (MineChess) -@factorization|因式分解 (Factorization) +@factorization|工厂化/因式分解 (Factorization) -project-red-base|红石计划 (ProjectRed) - 基础¨project-red-core|红石计划 (ProjectRed) +project-red-core|红石计划 (ProjectRed)¨project-red-base|红石计划 (ProjectRed) - 基础 dimensionaldoors|次元门 (Dimensional Doors)|896027 chickenchunks|区块载入器 (ChickenChunks) @@ -206,7 +206,7 @@ highlands|高地* doggy-talents|小狗天才*|713458 plasmacraft|电浆工艺 (PlasmaCraft) -fossils|考古与化石 (Fossils and Archeology Revival)|856874 +fossils@fossils-and-archeology-revival|考古与化石 (Fossils and Archeology Revival)|856874 glenns-gases|格林的气体 (Glenn's Gases) the-lord-of-the-rings-mod-legacy|魔戒:传承 (The Lord of the Rings Mod: Legacy)|363128 thaumic-tinkerer|神秘工匠* @@ -214,14 +214,14 @@ thaumic-tinkerer|神秘工匠* xycraft -chococraft|陆行鸟 (ChocoCraft) -pams-harvestcraft|潘马斯农场 (Pam's HarvestCraft)|669856 +chococraft@|陆行鸟 (ChocoCraft) +pams-harvestcraft@|潘马斯农场 (Pam's HarvestCraft)|669856 opis openblocks|开放式方块 (OpenBlocks) - +water-power|水力科技*|190676 randomcraft-v0-1-0|随机工艺 (RandomCraft) @@ -256,7 +256,7 @@ gravitation-suite|重力装甲*|734193 redstone-arsenal|红石兵工厂*¨@redstone-arsenal|红石兵工厂 (Redstone Arsenal) big-reactors|大型反应堆*|348989 -endertanks|末影水槽 (Ender Tanks) +endertanks|末影水槽 (Ender Tanks) - Forge¨endertanks-fabric|末影水槽 (Ender Tanks) - Fabric applied-energistics-2@ae2|应用能源2*|798231 hopper-ducts|漏斗管道* autofish|自动钓鱼*|630098 @@ -267,7 +267,7 @@ wireless-redstone-cbe|无线红石 (Wireless Redstone)|621428¨wr-cbe-universal| per-fabrica-ad-astra-geologica|自合成至星辰:真实地理 (Per Fabrica Ad Astra: Geologica) redstone-furnace|红石炉* -pneumaticcraft|气动工艺 (PneumaticCraft: Repressurized) - 1.8及更旧版本¨pneumaticcraft-repressurized|气动工艺 (PneumaticCraft: Repressurized) - 1.12及更新版本¨@pneumaticcraft-repressurized|气动工艺 (PneumaticCraft: Repressurized) +pneumaticcraft-repressurized|气动工艺 (PneumaticCraft: Repressurized)¨@pneumaticcraft-repressurized|气动工艺 (PneumaticCraft: Repressurized) tubes|管道 (Tubes!) @@ -283,7 +283,7 @@ project-bench|高级工作台* apples-plus|更多苹果 (Apples+) -qcraft|量子物理 (QCraft) + mad-science|疯狂科学* @@ -293,7 +293,7 @@ the-erebus|混沌之地*|856742 emerald-mod|绿宝石Mod*|282677 lucky-block-official|幸运方块 (Lucky Block) dynamic-lights|动态光源*|593066 -yarr-cute-mob-models-remake|怪物娘化 (Yarr Cute Mob Models - Remake)|172434 + monster-hunter-frontier-craft|怪物猎人边境工艺* zombie-awareness|僵尸意识* remote-io|远程接口 (Remote IO) @@ -358,7 +358,7 @@ falling-meteors-mod|陨石 (Falling Meteors) glacia-dimension|冰河时代 (Glacia)|1028541 -water-power|水力科技*|190676 + realtrainmod|真实火车 (RealTrainMod)|781058 applemilktea2|苹果奶茶2 (AppleMilkTea2)|466114 @@ -374,7 +374,7 @@ notenoughkeys|更好的按键控制 (Not Enough Keys) - +crafting-pillar-mod|合成柱* ultratech|次元科技 (UltraTech) atomicstrykers-infernal-mobs|稀有精英怪 (AtomicStryker's Infernal Mobs)|592395 @@ -384,7 +384,7 @@ pchan3-steamship-steamboat-pirates|飞艇与汽船 (SteamShip - SteamBoat and Pi thaumic-energistics|神秘能源*|791638 - +chisel|凿子*|641383 nuclearcraft-mod|核电工艺 (NuclearCraft) opencomputers|开放式电脑 (Open Computers)|618383 elemental-stones|元素之石* @@ -445,7 +445,7 @@ buildcraft-additions|建筑扩充* bedrockium-mod|基岩工艺* blocksteps thaumic-horizons|神秘视界* -modtweaker +modtweaker@ lootbags|战利品 (Loot Bags)|720841 compact-lava-generator|高级地热发电机*|522808 project-zulu|祖鲁* @@ -469,7 +469,7 @@ cooking-for-blockheads|懒人厨房 (Cooking for Blockheads) - Forge|545680¨coo psi@ good-nights-sleep|夜眠梦境/晚安的梦境 (Good Night's Sleep)|965322 -binnies-mods|宾尼的模组合集 (Binnie's Mods) +binnies-mods@|宾尼的模组合集 (Binnie's Mods) baubles|饰品栏* more-swords-mod|更多剑 (MoreSwords) thaumic-bases|神秘基础学* - 官方版1.7.10¨thaumic-bases-unofficial|神秘基础学 (Thaumic Bases) - 非官方版1.12.2 @@ -486,7 +486,7 @@ warp-book|传送书* ex-compressum|压缩工具*|676165 simply-jetpacks|简易喷气背包* blood-arsenal|血液兵工厂*|958995 -sim-u-kraft|模拟城市 (Sim-U-Kraft) + superheroes-unlimited|超级英雄无限*|693397 oceancraft-mod|海洋世界 (Oceancraft) @@ -524,16 +524,16 @@ keeping-inventory||592605 orespawn|矿石菌种 (OreSpawn)|464954 reliquary-v1-3|圣遗物 (Reliquary Reincarnations (Reliquary) / Xeno's Reliquary) - 1.8.9~最新版¨reliquary|圣遗物 (Reliquary Reincarnations* / Xeno's Reliquary) - 1.5.2和1.7.10 mo-bends|更多弯曲 (Mo' Bends)|205695 -quark|夸克*|1155355 +quark@|夸克*|1155355 -official-divinerpg@divinerpg|神圣RPG (DivineRPG)|1080271 +official-divinerpg|神圣RPG (DivineRPG)|1080271¨@divinerpg|神圣RPG (DivineRPG)|1080271 harvest-festival|丰收物语*|658968 tough-as-nails|意志坚定*|849264 non-update|不再有更新* clayium|粘土工业* better-storage|更好的储存* davincis-vessels|达芬奇之舟/达芬奇的船*|475292 -bspkrscore|bspkrs核心 (bspkrsCore)|430478¨bspkrscore-updated|bspkrs核心 (bspkrsCore) +bspkrscore-updated|bspkrs核心 (bspkrsCore)¨bspkrscore|bspkrs核心 (bspkrsCore)|430478 techguns|科技枪*|631578 Iron-Backpacks|特殊背包* openmodularturrets|开放式炮台 (OpenModularTurrets) @@ -544,7 +544,7 @@ dragonapi electricraft|电力工艺 (ElectriCraft) candycraft|糖果世界 (CandyCraft)|459723 -minefantasy2|我的幻想2 (MineFantasy2) + rails-of-war|铁路战争 (Rails of War) actually-additions|实用拓展*|841281 @@ -575,11 +575,11 @@ llibrary projecte|等价交换重制版 (ProjectE)|1109380 -@redpower2-core|红石力量2 (RedPower2)|71843 +customthings|自定义物品 (CustomThings) automagy|自动化魔法* - +minefantasy2|我的幻想2 (MineFantasy2) environmental-tech@|环境科技*|644622 magic-cookies|魔法饼干*|710008 @@ -592,7 +592,7 @@ another-one-bites-the-dust mine-mine-no-mi@|恶魔果实 (Mine Mine no Mi)|828931 advanced-rocketry|高级火箭*|671760 - +vics-modern-warfare-mod|维克的现代战争 (Vic's Modern Warfare Mod)|575936 chance-cubes|机会方块* gun-customization endercore@|末影核心 (EnderCore) @@ -616,15 +616,15 @@ dummycore¨dummycore-unofficial extra-utilities|更多实用设备*|868981 openmodslib|开放式模组库 (OpenModsLib) embers|余烬* -neat|极简血量显示* - Forge|938958¨neat-fabric-quilt|极简血量显示 (Neat) - fabric和quilt版本|938958¨@neat|极简血量显示 (Neat)|938958 +neat|极简血量显示* - Forge|938958¨neat-fabric-quilt|极简血量显示 (Neat) - Fabric & Quilt|938958¨@neat|极简血量显示 (Neat)|938958 mob-properties|怪物属性* -ganys-surface|Gany的世界 (Gany's Surface)|450171 + ezstorage|EZ存储 (EZStorage) - +ichunutil intangible -forge-multipart-cbe|CB Multipart/Forge Multipart CBE/ForgeMultipart - 1.10.2及以上版本¨forge-multi-part|CB Multipart/Forge Multipart CBE/ForgeMultipart - 1.7.10及以下版本 +forge-multipart-cbe|CB Multipart / Forge Multipart CBE / ForgeMultipart - 1.10.2及以上版本¨forge-multi-part|CB Multipart / Forge Multipart CBE / ForgeMultipart - 1.7.10及以下版本 the-titans-mod|泰坦生物 (The Titans)|730072 enchiridion stimmedcow-nomorerecipeconflict|合成冲突消除 (NoMoreRecipeConflict)|237776 @@ -666,7 +666,7 @@ valkyrielib@ waila-harvestability||608318 mixed-magic|混合魔法* hwyla||648893 -crafttweaker||769481 +crafttweaker@||769481 real-time-chat-translation-mod|实时聊天翻译* @@ -688,14 +688,14 @@ project-73926|超级合成框架 (Super Crafting Frame) akashic-tome|阿卡什宝典*|805606 morph-o-tool|变形工具 (Morph-o-Tool) decocraft@|装饰工艺* -refined-storage|精致存储*|1303245 +refined-storage@|精致存储*|1303245 deep-resonance@|深度共振* clockwork-phase|时之沙* -autoreglib +autoreglib@ roots|根源魔法* guide-api|指南 (Guide-Api) fraghappy-reborn @@ -706,13 +706,13 @@ router-reborn|路由重生* coroutil vanilla-magic|原生魔法* taoism|御灵*|668355 -essentialcraft-4-unofficial|源质魔法4非官方版 (EssentialCraft 4 Unofficial) + celestial-craft|天体* lost-thaumaturgy|失落神秘 (Pengu's Lost Thaumaturgy) compact-machines|更多空间/压缩空间* ex-nihilo-omnia|无中生有-世间万物*|631637 -last-sword-you-will-ever-need-mo|最终之剑 (The Last Sword You Will Ever Need Mod) +last-sword-you-will-ever-need-mo|最终之剑 (The Last Sword You Will Ever Need Mod)|399980 the-last-sword-you-will-ever-need-reboot|最终之剑:重生* ftb-library-legacy-forge|FTB Library(旧版) (FTB Library (Forge) (Legacy)) @@ -786,7 +786,7 @@ snad|子沙* storage-drawers-extras solar-flux-reborn|日光通量:重制版* simple-void-world|简单虚空世界*|1089311 -shetiphiancore¨shetiphiancore-fabric +shetiphiancore-fabric shadowmc scannable|扫描器*|692037 rebornstorage|存储重置 (Reborn Storage) @@ -832,7 +832,7 @@ re-zero-kara-hajimeru-isekai-seikatsu|RE:从零开始的异世界 (Re: Zero kara minewatch||730508 thaumcraft-inventory-scanning|神秘时代物品栏扫描* cyclic|循环*|648189 -chisel|凿子*|641383 + redstoneplusplus|红石++ (Redstone++) ctm|连接纹理 (ConnectedTexturesMod) simulated-nights|夜晚模拟* @@ -870,7 +870,7 @@ omnis-core auto-paths|自动路径* react shared-resource-packs|共享资源包* -i-know-what-im-doing|我知道我在干什么 (I Know What I'm Doing) +i-know-what-im-doing@|我知道我在干什么 (I Know What I'm Doing) Leather-Works|皮革加工厂* cyberware|机械改造*|618353 thermal-cultivation@|热力农业* @@ -911,9 +911,9 @@ back-to-eco|生态回归 (Back to Eco) world-of-warcraft|魔兽世界* the-witcher-adventure|猎魔人* fancy-battleaxes|华丽战斧* -zora-no-densha|Zora的电车 (Zora no Densha)|1085883 + roguelike-dungeons|冒险地牢*|300785 -first-aid|急救/医疗护理*|850687 +first-aid@firstaid|急救/医疗护理*|850687 bonfires|篝火*|772983 immersive-craft@|沉浸工艺* aroma1997core @@ -935,7 +935,7 @@ improved-extraction|矿物获取改进* ecology-mod|生态* cakeisalie-mod|蛋糕是个谎言 (CakeIsALie) ex-nihilo-adscensio|无中生有:Adscensio* -bookshelf +bookshelf@bookshelf-lib random-things|随意作品*|938101 @@ -974,8 +974,8 @@ streams|溪流* blockcraftery|方块工匠* bedrockores|基岩矿簇 (Bedrock Ores)|776822 chat-bubbles|聊天泡泡* -refined-storage-addons|精致存储附属* -foamfix-for-minecraft@foamfix|泡沫修复 (FoamFix)|678442 +refined-storage-addons@|精致存储附属* +foamfix-optimization-mod@foamfix|泡沫修复 (FoamFix)|678442 industrial-foregoing|工业先锋*|774112 expanded-armory|扩展装备* voxelmap|体素地图 (VoxelMap)|177583 @@ -1018,7 +1018,7 @@ wrapup nuclear-physics wither-skeleton-tweaks|凋灵骷髅调整* heat-and-climate|热量与气候* -customthings|自定义物品 (CustomThings) + exgregilo|格雷矿筛 (ExGregilo) placebo plants @@ -1104,7 +1104,7 @@ netherutils bonsai-trees|盆栽*|785239 -misty-world|朦胧世界* +misty-world@|朦胧世界* beta-kathairis|卡塞瑞斯 (Kathairis/Kether) - 1.12.2|865377¨kathairis-alive|卡塞瑞斯 (Kathairis/Kether) - 1.13+|865377 @@ -1149,7 +1149,7 @@ planar-artifice|镜面艺术: 传承 (Planar Artifice: Continuation) Requiem|安魂曲 (Requiem/Dissolution/Tartaros)|843736 slashblade-in-tfc|古刀传 (Slash Blade in TFC)|789943 -mlp-mythical-creatures|神话生物 (Mythical Creatures) +mlp-mythical-creatures-updated-to-1-7-10|神话生物 (Mythical Creatures) metamorph|变形*|723273 reactioncraft-3-rebirth|世界反应3:重置 (Reactioncraft 3: Rebirth) pewter|魔法匠魂* @@ -1160,7 +1160,7 @@ server-properties-for-lan|简单联机 (Server.Properties for LAN)|781904 jaopcaadditions|JAOPCA附属 (JAOPCAAdditions) cloche-call mouse-tweaks@|鼠标手势*|69677 -jaopcaagriculture|JAOPCA神秘农业附属 (JAOPCA Agriculture) +jaopcaagriculture|JAOPCA神秘农业附属 (Just A Resource Generation Compatibility Attempt) sky-resources|空岛资源*|817623 hexxit-gear|Hexxit装备* @@ -1331,7 +1331,7 @@ armoreablemobs|怪物装备自定义 (ArmoreableMobs) gem-buffs-infinite-potion-effects|宝石效果/增益宝石 (Gem Buffs) advanced-inventory -crafting-pillar-mod|合成柱* + pollution-of-the-realms|环境污染/领域的污染 (Pollution of the Realms)|1410539 @@ -1615,7 +1615,7 @@ integrated-terminals|集成终端* mekanism-tools@|通用机械工具* roboticparts|机械改造1.12+ (Robotic Parts) rewired -@biomesyougo|你将去的生物群系 (Oh The Biomes You'll Go) +oh-the-biomes-youll-go|你将去的生物群系 (Oh The Biomes You'll Go) - Forge¨oh-the-biomes-youll-go-fabric|你将去的生物群系 (Oh The Biomes You'll Go) - Fabric vertically-stacked-dimensions|垂直堆叠的世界* malisisswitches @@ -1774,12 +1774,12 @@ up-and-down-and-all-around|颠倒世界* lost-souls@|失落的灵魂* intelligent-energistics|智能能源* ender-compass|末影指南针*|785069 -ic2-tweaker + thaumic-gadgets|神秘宝具* multi-mine|多人开采* extra-foam-for-liteloader|Liteloader修复 (Extra Foam For LiteLoader)|982739 embersified|余烬能量转换* -ichunutil + thaumic-grid|神秘网络* back-tools|工具后置/后背工具展示* mapwriter-2|地图作者2*|680409 @@ -1791,13 +1791,13 @@ dm2|龙骑士2 (Dragon Mounts 2) tiquality loot-capacitor-tooltips|电容信息显示*|888624 classic-bars|经典状态条* -oh-the-biomes-youll-go|你将去的生物群系 (Oh The Biomes You'll Go) - Forge¨oh-the-biomes-youll-go-fabric|你将去的生物群系 (Oh The Biomes You'll Go) - Fabric +@biomesyougo|你将去的生物群系 (Oh The Biomes You'll Go) overground-ores|地表矿石* touhou-little-maid|车万女仆*|882845 eureka lazy-ae2|懒人AE2 (Lazy AE2) -libnine + equaldragons berries infinity-gauntlet-mod|无限手套 (Infinity Gauntlet) @@ -2101,7 +2101,7 @@ spatialcompat|空间兼容 (SpatialCompat) extended-reach no-recipe-book|没有配方书* darknesslib -terrafirmacraft|群峦传说:次世代 (TerraFirmaCraft: The Next Generation)|887732¨tfcraft|群峦传说 (TerraFirmaCraft)|887732 +tfcraft|群峦传说 (TerraFirmaCraft)|887732¨terrafirmacraft|群峦传说:次世代 (TerraFirmaCraft: The Next Generation)|887732 redstone-control|红石控制* snow-real-magic|雪!真实的魔法!⛄ (Snow! Real Magic! ⛄) - Forge|871191¨snow-real-magic-fabric|雪!真实的魔法!⛄ (Snow! Real Magic! ⛄) - Fabric|871191¨@snow-real-magic|雪!真实的魔法!⛄ (Snow! Real Magic! ⛄)|871191 comforts|舒适用品* - Forge / Fabric / Quilt¨comforts-fabric|舒适用品 (Comforts) - Fabric 1.16.5-1.19.2¨@comforts|舒适用品 (Comforts) @@ -2303,7 +2303,7 @@ amun-ra mysterious-mountain-lib|妖怪之山通用库 (MysteriousMountainLib) - +kitchen-mod|厨房 (The Kitchen Mod)|295019 baubles-reborn|饰品栏重置版* pams-brewcraft|潘马斯酿造工艺 (Pam's Brewcraft) @@ -2415,7 +2415,7 @@ chunk-in-a-globe|球内区块* dragon-mounts-legacy|龙骑士重置版 (Dragon Mounts: Legacy) fruit-trees|果树 🍊 (Fruit Trees 🍊)|976516 journey-into-the-light-mod|光明之旅·神之本质 (Journey Into the Light · Essence of the Gods)|1128195 -kitchen-mod|厨房 (The Kitchen Mod)|295019 + more-dungeons|更多地牢* voyage idealland|理想境*|1051328 @@ -2464,7 +2464,7 @@ moddirector function-api targetingapi re-targetingapi - +libnine dominionlib endergetic|末地拓展 (The Endergetic Expansion) @@ -2784,7 +2784,7 @@ mekanism-fluxified|“通量”机械* stepup|平滑自动上坡 (StepUp) @sodium|钠 (Sodium)|1079347¨sodium|钠*|1079347 - +netherlicious||1104913 yungs-better-mineshafts-forge|YUNG的矿井优化 (YUNG's Better Mineshafts) - Forge¨yungs-better-mineshafts-fabric|YUNG的矿井优化 (YUNG's Better Mineshafts) - Fabric¨@yungs-better-mineshafts|YUNG的矿井优化 (YUNG's Better Mineshafts) terrible-chest|可怕的箱子/可怖箱子*|922554 spartan-shields|斯巴达之盾*|672959 @@ -2856,7 +2856,7 @@ jei-enchantment-info|JEI附魔信息 (JEI Enchantment Info) project-red-fabrication|红石计划:制作 (Project Red - Fabrication) project-red-integration|红石计划:集成 (Project Red - Integration) -project-red-lighting|红石计划:照明 (Project Red - Lighting)¨project-red-illumination|红石计划:照明 (Project Red - Illumination) +project-red-illumination|红石计划:照明 (Project Red - Illumination)¨project-red-lighting|红石计划:照明 (Project Red - Lighting) project-red-world|红石计划:探索 (Project Red - World) project-red-compat|红石计划:兼容性 (Project Red - Compat) canvas-renderer|Canvas渲染器*|1211675¨@canvas|Canvas渲染器 (Canvas Renderer)|1211675 @@ -2917,7 +2917,7 @@ upgrade-aquatic|碧海新生*|962459 tfc-ambiental macaws-trapdoors|Macaw的活板门 (Macaw's Trapdoors) zenutil - +instant-bubble-studio|瞬息气泡工坊*|1103368 better-beacon-effect|更好的信标效果* netherite-horse-armor|下界合金马铠* tacocraft-fabric @@ -2925,7 +2925,7 @@ lootr¨lootr-fabric biolib@bio-library scavenge¨documentation|Scavenge - WIKI: 模组文档(1.10.2-1.12.2) - +heaven-earth-ring|乾坤戒指*|1113665 pams-harvestcraft-2-food-extended|潘马斯农场2 - 食物拓展 (Pam's HarvestCraft 2 - Food Extended) @@ -2955,7 +2955,7 @@ lambdynamiclights@|Lambda的动态光源 (LambDynamicLights)|1082100 realistic-torches|真实火把*|566201 durability-viewer|耐久显示器*|1086168 -netherlicious||1104913 + ground-item-highlighting|地面物品高光显示*|1084931 @@ -2976,7 +2976,7 @@ packagedexcrafting|封包合成拓展 (PackagedExCrafting) compactstorage|紧凑存储 (compactstorage) useful-backpacks|实用背包* -conquest-reforged|征服者 (ConquestReforged)|1431287 +@conquest-reforged|征服者 (ConquestReforged)|1431287 steelseries-gamesense pizzaatimes-timber-mod tellme @@ -3006,7 +3006,7 @@ server-translations image2map@ corpse@|遗体* ring-of-the-miner-fabric|矿工戒指 (Ring of the Miner) - Fabric¨ring-of-the-miner-forge|矿工戒指 (Ring of the Miner) - Forge -instant-bubble-studio|瞬息气泡工坊*|1103368 + @@ -3016,7 +3016,7 @@ kaleido crock-pot|烹饪锅* -heaven-earth-ring|乾坤戒指*|1113665 + @@ -3059,7 +3059,7 @@ entity-culling|实体渲染机制优化* hammerandvil|锻造锤与锤锻台 (HammerAndVil) simply-backpacks|简易背包* -mermod-fabric¨mermod-forge|Mermod / Mermaid Tail Mod - 1.19.2~1.18.2 Forge版本 +@mermod better-placement|更好的放置*|730136 fat-cat|大资本家* waystones2waypoints|传送石碑路径点 (Waystones2Waypoints) @@ -3190,7 +3190,7 @@ ambience-extras|环境音乐:附加 (Ambience - Extras) additions-mod extracpus|更多CPU (Extra CPUs) extra-crafting-storage -@conquest-reforged|征服者 (ConquestReforged)|1431287 +conquest-reforged|征服者 (ConquestReforged)|1431287 underground-city-engineering@|地下城市工程*|943842 essentials soul-shards-respawn @@ -3246,7 +3246,7 @@ randompatches-integration bubble-column-elevator-backport|1.13水域机制 (1.13 Water Mechanics) basic-nether-ores|基础下界矿石*|1136294 nuclearcraft-reactor-builder|核电工艺反应堆建造机 (NuclearCraft Reactor Builder) -pams-harvestcraft-2-food-core|潘马斯农场2 - 食物核心 (Pam's HarvestCraft 2 - Food Core)|1313245 +pams-harvestcraft-2-food-core|潘马斯农场2 - 食物核心 (Pam's HarvestCraft 2 - Food Core)|1000336 eki-lib nodami|伤害免疫机制移除 (No Damage Immunity) @@ -3750,14 +3750,14 @@ necromancy library-of-exile variegated - +chest-cavity@|胸腔* extended-note-block|音符盒扩展 (Extended Noteblocks) hbms-nuclear-tech-mod-reloaded|HBM的核科技重制版 (Hbm's Nuclear Tech Mod Reloaded) loading-timer@|加载时长显示* ocrocketry chime-fabric@chime witherite@|凋灵合金* -mermaid-tail-mod|人鱼尾*¨@mermod +mermod-fabric¨mermaid-tail-mod|人鱼尾*¨mermod-forge|Mermod / Mermaid Tail Mod - 1.19.2~1.18.2 Forge版本 astromine-core|天体矿工:核心 (Astromine: Core) dungeondq dynamic-trees-integrated-dynamics|动态的树:动态联合/集成动力附属 (Dynamic Trees - Integrated Dynamics) @@ -4085,7 +4085,7 @@ ticktock@servertick fps-reducre-for-fabric|FPS减速器(Fabric) (FPS Reducer (For Fabric)) art-artillery|艺术?火炮! (Art? Artillery!) -minemenu-refabricated@minemenufabric|我的菜单 Fabric 版 (MineMenuFabric) +minemenufabric@|我的菜单 Fabric 版 (MineMenuFabric) @tabtps @afkpeace¨afkpeace @@ -4112,7 +4112,7 @@ village-employment|村庄就业* deathlog@|死亡日志* automatic-elytra|自动鞘翅* - +axolotl-bucket-fix|美西螈桶修复* xks-decoration|XK的装饰 (XK's Deco)|1214362 endgame-materials-addons|更多终局材料* ravage-and-cabbage @@ -4556,7 +4556,7 @@ multi-world terralith¨@terralith overworld-two¨overworld-two-forge - +xl-packets|XL数据包 (XL Packets) - Forge¨xl-packets-fabric|XL数据包 (XL Packets) - Fabric rftools-storage@|RF工具:存储 (RFTools Storage) rftools-builder@|RF工具:建造机 (RFTools Builder) @@ -4724,7 +4724,7 @@ desolation-forge|荒芜 (Desolation) - Forge¨desolation|荒芜* - Fabric ma-astral zaraklib chipped@ -chest-cavity@|胸腔* + mending-for-1-7-10|给老版本的经验修补 (Mending for Older Version) unreachable-fantasy-mod ai-reducer|AI减速器 (AI Reducer) @@ -4963,7 +4963,7 @@ one-block-forge human-reborn @addadd|额外扩展 (Additional Additions)|1427321¨additional-additions|额外扩展* - Fabric|1427321¨additional-additions-forge|额外扩展 (Additional Additions) - Forge|1427321 earlygame@ -happiness-is-a-warm-gun@ + kvc-blocks|蛋挞君的原版补全方块篇 (KVCBlocks)|1239534 enigtech2-util @@ -5073,7 +5073,7 @@ food-effects item-borders|物品边框* - Forge¨item-borders-fabric|物品边框 (Item Borders) - Fabric iceberg|冰山* - Forge¨iceberg-fabric|冰山 (Iceberg) - Fabric¨@iceberg|冰山 (Iceberg) - +worldeditcui-forge-edition-3 enhanced-farming@|增强农业* @@ -5254,7 +5254,7 @@ durability101@ ceramic-shears|陶瓷剪刀* torch-bandolier|火把弹药袋* rapid-leaf-decay - +netherite-horse-armor-mod|下界合金马铠* ambient-environment glass-cutter dicemc-tiered-armors @@ -5420,7 +5420,7 @@ compass-manhunt exlines-furniture tinkers-archery|工匠箭术 (Tinkers' Archery) designer-modeling|设计师建模* -netherite-horse-armor-mod|下界合金马铠* + netherite-horse-armor-fabric|下界合金马铠 (Netherite Horse Armor) netherite-elytra|下界合金鞘翅* @@ -5952,7 +5952,7 @@ fabricskyboxes@ @fvt blue_skies_tcon tdv-tweaks - +ic2-tweaker picture-in-picture-pip guilt-trip parcool|跑酷! (ParCool!) @@ -6042,7 +6042,7 @@ the-one-probe-fabric gehenna-super-spooky-nether-overhaul-for-forge draconicminus hygiene -worldeditcui-forge-edition-3 + external-view-reload fluid-drawers-legacy|储液抽屉:遗产* my-server-is-compatible @@ -6659,7 +6659,7 @@ astemirs-fireflies ender-crop¨@ender-crop modupdater fivehead -axolotl-bucket-fix|美西螈桶修复* + attained-drops|可种植掉落物* attained-drops-2|可种植掉落物2* @@ -7797,7 +7797,7 @@ hungertweaker knob-control daedalus-labyrinth tip-the-gui|界面尺寸设置优化 (GUI Scale) -copper-overhaul + epic-fight-nbt-integration login-protection|登录保护* swem @@ -8252,7 +8252,7 @@ super-secret-settings @plumeconfig geophilic@ critter-fights -hypothermic@ + nosecrets|不秘密 (NoSecrets) extra-creeper-types loot-casket @@ -8759,7 +8759,7 @@ otg-skylands fxs-rail-optimization@rail-optimization morejs@ -@moresnowballls|更多雪球 (MoreSnowballs) + valkyrien-skies-control the-end-expanded-mod-ice-and-fire-addon @vault-patcher|保险库补丁 (Vault Patcher) @@ -8822,7 +8822,7 @@ dawn-to-dusk open-tower-defence|开放式塔防* mtr-station-decoration@station-decoration perviaminvenire - +hypothermic@ silverbirch@silver-birch @@ -9078,7 +9078,7 @@ commandspy slimyfloor daily-dad@|每日冷笑话*¨daily-dad-server@|每日冷笑话 (Daily Dad) - Daily Dad - Server good-ending -xl-packets|XL数据包 (XL Packets) - Forge¨xl-packets-fabric|XL数据包 (XL Packets) - Fabric + creeper-nuggets coherent-villages @@ -9204,7 +9204,7 @@ snowballing winter-essentials magnesium-rubidium-extra-potato-editon|镁/铷附属:土豆版 (Magnesium/Rubidium Extra: Potato Editon) @realistic-cruelty|真实残忍 (Realistic Cruelty) -trapper-pelts + workshops-of-doom create-mechanical-spawner|机械动力:动力刷怪笼* @@ -9849,7 +9849,7 @@ enchancement useful-spyglass@ @illagers-love-armor - +@moresnowballls|更多雪球 (MoreSnowballs) befriendmobs-days-with-monster-girls|友好化生物:与怪物娘的日常 (BefriendMobs: Days with Monster Girls) launchgui villager-armor-layer@|村民盔甲显示* @@ -9866,7 +9866,7 @@ extra-trades blood-particles-mod @two-weeks-hud fazcraft-mod -mod-menu-no-more@changelog +mod-menu-no-more@ @slime-craft way2wayfabric@ cloche-profit-peripheral @@ -9960,7 +9960,7 @@ enderio-unofficial|末影接口非官方版 (EnderIO Unofficial) dimensional-gravity - +happiness-is-a-warm-gun@ bring-color-to-my-skies portal-dungeons @@ -10217,7 +10217,7 @@ boat-break-fix advanced-movement totem-overhaul mobs-no-render@ - +copper-overhaul covenants-choose-your-purpose insatiable @@ -10466,4 +10466,735 @@ chunk-sending-forge-fabric overworld-mirror @toggleable-sonic-boom quicksort@ -fake-player@fake-players \ No newline at end of file +fake-player@fake-players +@lootbeams-fabric|战利品光束Fabric版 (LootBeamsFabric) +continuumation +ai-art-deco-1|AI装饰一号 (AI Art Deco 1) +@occlusion-culling-fix-sodium +easy-dungeons|简易地牢* +the-morbid-harvester-reborn-reborn|变态的凋灵收割者 (The Morbid Reborn Reborn) +ars-armiger +bettergolem +@end-poem-extension|终末之诗扩展补丁 (End Poem Extension)|1435381 +trampledisabler-fabric + +advanced-tfc-tech-unofficial|进阶群峦科技非官方版 (Advanced TFC Tech Unofficial) + +impostore +effishiency +glumbis +prehistoric-nature-fossils +peeled +butterflys +@forgedapi + +be-style-wither@ +campfire +ars-enderstorage +@ftbchecker +mythicmetals-decorations +metallurgychisel +sooty-chimneys-fabric@ + +mekanism-evolution + +chiseledadditions + +gregtech-4|格雷科技4移植版 (GregTech 4 Rewritten) +lacrimis +productive-villagers +canned-goods@ +itemchat-fabric@itemchat + +crafttweaker-sixik-utils-ftb-quest +wares@ + +recoil-shotgun@ + +observed@ +ex-sartagine-requiem@ +giant-pacman-1-16-5 +item-banning-fabric +erroring-entity-remover +mclo-gs +pink-expansion +polluted-earth-reborn +kitsus-forgecraft +stat-check +hit-indication@ +multipart-machines-farming + +lotrfixer +tru-first-person-mod +reduced-music-delay +agers-giant-junkyard-dimension +jsmacros +great-big-world +armored-mobs +inversia@ +end-expansion-the-lamented-islands + + + +@compromise|折衷 (Compromise) + +no-additional-repair-cost +cave-dust@|洞穴尘埃* + + + +create-applied-kinetics|机械动力:应用机械 (Create: Applied Kinetics) +nocubes-zombie-mobs +create-gourmet +medical +yellowbrosss-extras +@simply-caterpillar +cataclysm-delight +old-combat-mod@ +pipe-connector@ +diamethyst-arrows@ +cherry-blossom-grotto + + +parties +pots-and-mimics-rpg +@gemspark +otakomod +wall-jumping +new-life +reeves-furniture +@undying|不朽 (Undying) +creatania +boat-rocked +proficiency-honest-work +warrior-rage@ + +pokefood +armor-souls-reforged + +@the-aether-redux|天境:新生代 (The Aether: Redux) +@banner||1447687 +staaaaaaaaaaaack@ +thanks-i-have-rei@tihr + +satchels|挎包* +matex +weapons-of-the-apocalypse +tis-advanced@ + +toroquest-revamped + +tis-create +tektopia-trader +@worldtime +beasts +alphabet-speedrun|自定义速通挑战 (αβspeedrun)|1441945¨@alphabet-speedrun|自定义速通挑战 (αβspeedrun)|1441945 +@symlink-check|符号链接拦截 (Symlink Check)|1446393 +tis-stringify + +@dismount-entity +dyeable-compasses@|染色指南针* +supercompression|超级压缩 (Super Compression) +better-hungteen-s-plants-vs-zombies|更好的HTPVZ (bhtpvz) +piglinsafety|猪灵:多喝岩浆 (PiglinSafety) +gatewayer +@bocchud|孤独HUD (BoccHUD/MiniHUD-Forge) +arrowcollector|你返箭吗 (Arrow Collector) +saplingslayer|鸽子衔枝 (SaplingSlayer) +quality-quails +hydration + +halohud@ +@resourcify +ioclias@ +twilights-flavors-delight@twilight-delight|暮色风味乐事 (Twilight Flavors & Delight) +@clean-f3 + +architect-tools-converter-1-7-10|建筑师工具 - 转换器 (Architect Tools - Converter) + +@librarian-trade-finder +server-resourcepack-checker@server-resource-pack-checker +main-menu-credits@ +@photon-editor|光子 (Photon) +@voice-chat-interaction +replay-voice-chat@ +ships-mod-unofficial|船:非官方版 (Cuchaz Ships Unofficial) + +storage-overhaul +chorus-warps +aureate-reliquary +submerged-explosions +netherite-items +aim-plus +reckless-drilling +tick-stasis@ +@azure-paxels + +no-angled-brackets +alien-apocalypse +server-utilities +su-addon-silk-spawners +su-addon-quests +harder-hardcore +argentinasdelight +colonies +@everythingcopper|万物皆铜 (Everything is Copper)|1447405 +passive-endermen@|被动型末影人* + +elemental-aspects +dune +lithereal +mobs-genus +ore-extension +extradelight +shady-alchemist +ender-knight +bubbleland +cake-cow + +shrimps +projecte-aether-addon +coal-additions +fungi-stew +grenade-launcher +illness +structural-statues + +wild-bushes +mutant-wolf +curses-naturals + +straighten-up@ +twilight-catalist|暮色催化石* +extra-moas +corruptional +@enablemultiplayermode +wizardy-delight + + +@tinyallies +all-da-nuggets +random-recipes +starks|史塔克一家* +easy-paper +modern-ruins-pack +tetras-delight + +avaritia-reset-version|无尽贪婪重置版* +archeology-plus@ +mobification +trimseffects +tough-glass@ +celestisynth|天神剑技* + +more-axolotls@more-axolotl +@perfect-accuracy +@perfect-accuracy-forge +@swingthrough +log-begone@ +@transferable-pets +@bone-blocks +woodster@ +deaths-mark +sweet-boreal@ +fuzzymobs +@beslandia +@ars-magica-25|魔法艺术2.5 (Ars Magica 2.5) +delightful-froge + +new-world-height-and-depth +@inertiaanticheat + +scroll-of-harvests +@scripts-chunk-loaders +clean-tooltips@ + +quick-save@quicksave|快速存档&快速读档 (QSave&QLoad)|1448317 +iron-bows-forge¨iron-bows-fabric + +filtered-chests + + +@anti-xray +special-strength +armorpoints@ +easy-enchanting@|轻松附魔* +elitia +cosmetic-wings||385564 + +@death-backup +bear-with-me +create-diesel-generators +vintagefix + +nostalgic-tweaks@ +suspicious-sand-maker +create-cobblemon-industrialized +create-pixelfactory +ies-tweaks +@haunt-furnace +player-ladder +orb-of-origin-plus + +firework-minecart-mod@ +soots-sandwichcraft +rpg-origins +@zombie-lag-fix +classic-minecraft-icon@ +@the-binding-of-isaac-useful-items +@trivia +some-useful-ores +@stardew-valley-mod +rainy-harvest +mythic-mobs@ +lost-features@lostfeatures +bagus-lib@ + +@better-doors +@server-pack-unlocker¨3178320¨3178321 +x-enchant + +@blahaj-remake +pamps-simple-edition +@babyzombie-drops + +better-chiseled-bookshelves|更好的雕纹书架* +@shorks +@missing-vanilla-recipes +alcocraft + + +mowlib + + +not-just-biomes@ +agricultural-enhancements|农业增强* + + + +ramel@ + +@chat-tools +amys-rftl +cutepenguins|可爱的企鹅 (Cute Penguins) +hellions-sniffer@hellions-sniffer+ + +hybrid-flowers|杂交花* + + +@methane +mushroom-villagers +@litematica-server-paster +s33r-butterfly-paintings +@more-statistics +villager-see-villager-do¨@villager-see-villager-do + +silent-river-in-the-nether|炎液断蒸* + +sulfar-mod +apple-cows +gardening-tools +nether-ores-plus +river-treasures +lumbers-axe + +immunity-enchantments +better-fishing-rods +book-fishing +more-villager-trades +custom-world-icons@ +trims-begone +haste-enchantment +workshop-for-handsome-adventurer +desert-upgrade +ocean-recovery +more-armor-trims +all-the-trims@allthetrims +@bamboo-sign-editor|告示牌编辑器 (Bamboo Sign Editor) +revival-orb@ +stackable-trims +keepcuriosinventory|饰品栏优化 (KeepCuriosInventory) +megachicken +s33r-iris-azalea-mod +difficultyplus +black-lung¨@blacklung +friends-and-foes-beekeeper-hut-forge¨friends-and-foes-beekeeper-hut-fabric¨friends-and-foes-beekeeper-hut-quilt +friends-and-foes-flowery-mooblooms-forge¨friends-and-foes-flowery-mooblooms-fabric¨friends-and-foes-flowery-mooblooms-quilt +@random-looting|随机战利品表 (Random Looting) +kubejs-botany-pots@ +irons-rpg-tweaks +@enchantips +tokenable-decoration +@emogg +@festival-delicacies|节日佳肴 (Festival Delicacies) +elytra-trims@ + +gothic-rpg +@arcadian-dream|幻想之梦 (Arcadian Dream) + +dungeon-and-taverns +elytra-trims-extensions@ +fallingblocks +fabric-musica +growmeal +moa-decor-art + + +easier-armor-trim-duplication +steppy@ +new-thin-air|稀薄的空气 (Thin Air) +the-armory +xannoszs-better-minecarts|Xannosz的更好的矿车 (Xannosz's Better Minecarts) +meliusvanish@vanish +tooltip-tool-tips@ +sanity-descent-into-madness@ +@no-darkness-effect +@seamless-loading-screen-renewed +armorskin@ +@translucent-glass +simple-health-bar@ +plenty-of-golems +@hit_particles +@durability-viewer-updated +puffish-skills@skills +better-archeology + +apoca-mobs + +@snow-sniffer +responsive-shields@responsiveshields +beehave@|蜜蜂信息显示* + +sign-clipboard@ +nocubes-better-frogs +@vanilla-permissions +create-dreams-desires@create-dreams-and-desires +trails-and-tales@trails-and-tales-plus + +stackable127@ + + +wanderer-profession + +cataclysmic-combat +persona + +create-air-forge +@gravity-pads + + +@inhibited + +sekc-physics¨sekc-physics-fabric +@artificial-lightning-mod +do-api@ +@pythonmc +@tp-chat-cmd|TP 聊天命令 (TP Chat Command) +grabby-mobs@ +pandas-falling-trees@ +iron-ender-chests +@delightdulcreators + + + +dusty-decorations@ +simple-double-jump + +save-the-monument +snifferiety@ +light-the-night + +terrafirmaships +better-ocelots + +denyblocks +sculk-horn@-sculk-horn +ydms-red-panda +overworld-piglins +waveapi +modlist +panorama-screens@ +campfire-spawn-and-tweaks +ghostz@ +better-totem-of-undying¨better-totem-of-undying-fabric¨@better-totem-of-undying +modonomicon@ +moreyoyos +effortless-fabric +onetwenty-1-20-backport@trailstales-backport +xtra-arrows +secrets-of-forging-revelations-a-tetra-addon +trolldom +@made-in-china|中国制造 (Made In China) +soundlimitextends +artemis-flower-pots +sculky-bits +minecolonies-for-computercraft +flame-sweeping-potato-edition|烈焰横扫:土豆版* +skinned-carts +quick-effect@quickeffect +block-log + + +@gms|游戏模式选择器 (GameModeSwitcher) +dimension-localized-inventories +skillcraft-modpack-tool + + +@notsoessential +worldsalads-opulence + +@wyrms +pearfection +enderchests¨enderchests-fabric +dragon-rider +create-utilities-fabric + +creeperhost-presents-chickens +inet@ + +my-beloved-icon@ + +tetra-enlarged +fruit-delight@|果品乐事* +@idletweaks +tagmod +passive-skill-tree +villagertradefix + + + +fishermans-trap +@removebats|禁止蝙蝠生成 (RemoveBats) +nabba +enchantdemon|附恶魔 (EnchantDemon) + + +create-alchemists-industry@ + + + + + +@modern-sound +@polished-gui +all-bark-all-bite +lively-forests + +dynamic-fullbright@ +@modularwarfare-shining|模块化武装:烨熠 (ModularWarfare-Shining) +@dcch +difficulty-locker +virus-disease-mod +this-boat-is-mine@|这是我的船! (This boat is MINE!) +pandas-extra-details@ +ebs-wildfire + + +bobby-forge +matter-transporter +tall-flower-pots + +too-fast + +fast-paintings +personality-rp@personality + + +@fabric-yaml-configuration +comfy-beds +twerk-crop-dusting-mod-fabric +extra-damage-enchantments@ +lemon-core +rightclickharvest +planters-forge +passive-charms + + +@linkart +@jamlib +globalization +renewable-spore-blossoms +@paragraphs + +@scoreboard-helper|记分板助手 (Scoreboard Helper) + + + + + + + + + + +mobs-info@mobsinfo +@ngrok-api + + +@swordunsheather|利刃出鞘:重铸 (Sword Unsheathe Recasting) +rpgloot +@sus-saver + +dynamic-fps-reforged@ +utility-belt@ +cultivatecraft +rpgloot-patched +fluidlogged +create-foundry +@cobblemon-fight-or-flight +trapper-pelts@ +ender-magnet|末影磁铁* +dead-guys-hallucination-nightmare + + +simple-hot-air-balloons +@toomuchrain +thirst-was-remade@|口渴-重制* +ies-tweaks-fabric@ +brown-mooshrooms +@whetstones +@better-log4j-config +@harvest +wooden-hoppers +skeleton-horse-spawn +configanytime +faster-crouching +yeetusexperimentus +@xbob +zombie-horse-spawn +tinkers-katanas +fbp +@yeetem-potions +incapacitated¨@incapacitated +@fys|原版注册解封 (Forge Yourself) +@mcopper|梧桐物语 (MCopper) +no-ranged-knockback +industrial-agriculture +macaws-bridges-sajevius|Macaw的桥梁:Sajevius 附属 (Macaw's Bridges - Sajevius) +@getwebbed +@water-resistance-potion +the-predators +create-structures +ivycore@ +more-immersive-wires +ventur-origin@venturorigin + +@sodium-blendingregistry +waterphysics +ece-enhanced-celestial-enhancement|月亮事件扩展:第二月亮 (ECE - Enhanced Celestial Enhancement: The Second Moon) +plants-vs-zombies-cubed@pvzcubed + + + + +create-guardian-beam-defense +antisocial@ +expanding-technologies@ +despawning-eggs-hatch +mob-death-messages +chunkapi@ +@betterwaystonesmenu +@big-fish +@nulltal +@caffeinefilled +pet-transfer-trade-your-pets@pet-pass-trade-your-pets! +lets-do-bakery +searchables@ +dixtas-armory@ +@ingamestats +hanger-system-overhaul +@isaidnosnow +jarsauth|客户端验证 (JarsAuth) +autoelytrapanic¨@autoelytrapanic +more-crossbows-cj +multipart-machines-cooking + + +thepeebrain-dream-dimension + +@ochetgenyo +@qcraft|量子物理重构 (qCraft Reimagined) + +@todo-list|待办事项 (TODO List) +@arst-armor-hud +@spice-of-life-onion|生活调味料:洋葱版 (Spice of Life Onion) +effect-berries@ +tinkersworld + + +ragdoll-corpses|布娃娃尸体*|431874 +floatingruins|浮空遗迹 (FloatingRuins)|301900 + +creeper-backguard@creeperbackguard +audio-extension-for-fancymenu-forge +stay-warm-together +aeroblender +ancient-aether +artisticcraft@ +lavaboat_mod|熔岩船 (LavaBoat) +sophisticated-wolves|精致的狼 (Sophisticated wolves) +enderio-alloys +dynamic-music +@item-hunt +dynamo +create-sabers + +split-shulker-boxes@ +tinkers-leveling +curios-compat +@cloudbackup|云备份 (CloudBackup) +@serverpingerfixer +@world-days +such-a-delight +create-compressed +emoji-type@ + +tower-defense-units +barrier-block-mod|屏障方块* +@simplehealthorbs +misctweaks_ +dynamic-music-updated@ +spartan-netherite +climb-ladders-fast +@custom-machinery-mekanism#all-versions + + + +@noraidtotems +@snowballs_plus +bagus-mob +chat-twix@ +shulker-meat +accurate-block-placement-reborn +favorite-items + +@smart-villagers-follow-emeralds|见钱眼开 (Smart Villagers Follow Emeralds) + +@villager-pickup +step-it-up +fantasy-trees|奇幻树* + +remodified|你再造! (Remodifier) + + +@better-fusion-reactor-for-mekanism +@growableores +golemsarefriends + + +@keepcommandhistory + + +generatorgalore +@map-shirts + +dyenamicsandfriends + + +fusion-connected-textures@ +bluebox +hunger-reworked +armor-status-hud-renewed|耐久信息显示:重生 (Armor Status HUD Renewed) +boblib +placeable-mobs +potion-blender \ No newline at end of file