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