From e6c69750475593f6311c83bb33f0580c072667fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BE=99=E8=85=BE=E7=8C=AB=E8=B7=83?= <1043137532@qq.com>
Date: Tue, 18 Jul 2023 04:45:38 +0800
Subject: [PATCH] 2.6.4
---
Plain Craft Launcher 2/Application.xaml.vb | 10 +-
.../Controls/MyButton.xaml.vb | 21 +-
Plain Craft Launcher 2/Controls/MyCard.vb | 3 +
.../Controls/MyExtraButton.xaml.vb | 2 +-
.../Controls/MyHint.xaml.vb | 4 +-
.../Controls/MyIconTextButton.xaml | 9 +
.../Controls/MyIconTextButton.xaml.vb | 180 ++++
.../Controls/MyListItem.xaml.vb | 30 +-
.../Controls/MyLoading.xaml.vb | 4 +-
.../Controls/MyRadioButton.xaml | 2 +-
.../Controls/MySearchBox.xaml.vb | 6 +-
.../Controls/MyTextButton.vb | 4 +-
Plain Craft Launcher 2/FormMain.xaml | 2 +-
Plain Craft Launcher 2/FormMain.xaml.vb | 53 +-
.../Modules/Base/ModBase.vb | 8 +-
.../Modules/Base/MyBitmap.vb | 4 +-
.../Modules/Minecraft/ModComp.vb | 47 +-
.../Modules/Minecraft/ModCrash.vb | 103 +-
.../Modules/Minecraft/ModJava.vb | 37 +-
.../Modules/Minecraft/ModLaunch.vb | 30 +-
.../Modules/Minecraft/ModMinecraft.vb | 954 +-----------------
.../Modules/Minecraft/ModMod.vb | 903 +++++++++++++++++
.../Modules/Minecraft/ModModpack.vb | 28 +-
.../Modules/Minecraft/ModWatcher.vb | 2 +-
Plain Craft Launcher 2/Modules/ModEvent.vb | 16 +-
Plain Craft Launcher 2/Modules/ModMusic.vb | 4 +-
.../Modules/Reference/CookieWebClient.vb | 8 +-
.../My Project/AssemblyInfo.vb | 4 +-
.../Pages/PageDownload/ModDownloadLib.vb | 45 +-
.../PageDownload/PageDownloadInstall.xaml.vb | 25 +-
.../Pages/PageLaunch/MySkin.xaml.vb | 15 +-
.../Pages/PageLink/PageLinkHiper.xaml.vb | 1 +
.../Pages/PageLink/PageLinkIoi.xaml.vb | 1 +
.../Pages/PageSpeedLeft.xaml.vb | 4 +
.../Pages/PageVersion/PageVersionMod.xaml | 27 +-
.../Pages/PageVersion/PageVersionMod.xaml.vb | 377 +++++--
.../Pages/PageVersion/PageVersionSetup.xaml | 6 +-
.../Plain Craft Launcher 2.vbproj | 8 +
Plain Craft Launcher 2/Resources/Custom.xaml | 32 +-
Plain Craft Launcher 2/Resources/Help.zip | Bin 68561 -> 70960 bytes
Plain Craft Launcher 2/Resources/ModData.txt | 917 +++++++++++++++--
41 files changed, 2616 insertions(+), 1320 deletions(-)
create mode 100644 Plain Craft Launcher 2/Controls/MyIconTextButton.xaml
create mode 100644 Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb
create mode 100644 Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
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 49a5e43ee2c618662d458aabbc871f90c561575a..d2a0c1a194fbbf0c1c2a388b4a1d01e17cfec4ca 100644
GIT binary patch
delta 42685
zcmb5VV{;}Hl{k%uZ<)m&0I!|=z#~wmFb?mkr3P1+@
z?gr^Wn^aR6@uTU@1+SF9%SZR5{sXM)3vDJ`V_197wwH*UG`;1uP3!_e-v8bMQJf>lO^yP1wmj)MA`+TmXn2<*-7qjmL;b#a7g
zbM<)|K-mK}h`?(FKtE{IPV-S`^LQEF5y!>sF8EBE4k7Q;ugBAo>2M8?lz?RdD@}vs
zx}1s_A
z1s82$-Q@XBLdy5DT>G6v3?Q}kFZ2qAhx2ae15PBTEe-R$f!qGzRmNPnQ9Jd$2AVBZ
zKHq^Xzd74m=8u6?JA$un)%-`rG{;1i0a=eLl%HQ;K%su{`{9x
zpU2$WFr|!ds<+o>hdnH5g(=a_*EygrEXiueq$Wcogiek*5U)8-p`no#0v@#43lc@y
ziq%RaJ(Cfu7Bry)*G_j%FYv$>NEnB;sBDceK120xG4aINq+AK+A>_*kcpeB75*-onfn!&jwma%I1xywf(Qr&S+36}PBW}6F=s*P@eCYR1Wq?C~
z39Pn{W_eUYilU((Lkl4BN{)3t20SvgR2!09S3T*S@`g2`|aQH
zz_cbsp_H|roFOE)#>W$00_IrOA~tEHgy@CSBCnl)YNb-|!Rcf_Xf^>;1>H(S_E&Wt
zt3X%wx7r-RL_$OypnE<>U-_e+QZpzWpoPWV^w?A)HGy6Vw@ps5VdDJIl5D6>B`x1=
zm9s3}2^oBaoCirDOtEmnf*TUAlfU9V7)!ex`X&Zg9&uEu-R3PeX^qT+D~^OJ`;^+<
zUnjO~HbU00U*FsU$IjfWlZgcFFL*kQtUgUIrB+bsOo*5WoI|Q)*&8ZWmhEMd)f5J!
zbDspGSC+14sH0=>fAX2DKDA5TC1K4C^)$|O(cg@;VdtJw?gAGL!5<0XUmyH9!ZPB`f
zo_qJ0LlT1XQlXKxRD_1DaG1quN`F@pqY?ipoLgXQu*C@}my9hd%Pi7dBH9ynxYKoh-_gy!Y-h8oJy^Kr#9xCar5Pt9*FWOorWBm*je@nu%E8w
z;dn_HxKs>E4dzLqaTIvMyHnJ(Ja5bmnQc(kv+f=c5*@wehV-}B-Rg>kGH=6Uh3H_v
z2O>Bi0v*Xbz^56*!L|sff?pgGbFps`{s*@$b~dR~{=;(6hAf;BFx{j9Q9xg4uO3d7
z|NEvr9%34`P~ICu33a=(Fk(tyY%W;(@%>CdCGJezDa)mZ+pHBNiqs>rYTIBP2o+NL
zMfe6?r&Gu0=PHs(f7ryCV}RG!?b)q%@6y4xkz-;n0Y5O$lkRthObX15jdKmRiG-wi
zstIDyAcPmHP-3wHCpQU7fP&~p1u(kfYlj`9&!7j2QZBX$Z9_keNI05V;Ly*LBd@3R
zuuTBP{5bEY2dSw!q0&o%?sSSYhB*{b#NIFwCh#^{+aS4^k1v`TveBJp0%bXaoMn8D
z5j95NImJt!KXMLAceUd!M
zs|~f@oDx+11L9VF9E1Ct;;v}!>+2~)UlF!nf-fiE0_L?EKj}ys!atx{REvj+O83%s
zh>cCO2;4yUehmtMevu^~KUuA;7gx;v_=^qRnH#va=?RJ#4G9}#+*lq?-eS%p%0Dr8
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
delta 40121
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