From 2c7b532188a40f3195f777a85d695df1f9209a3d 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: Mon, 27 Nov 2023 00:49:59 +0800 Subject: [PATCH] 2.6.11 --- Plain Craft Launcher 2/Application.xaml | 2 +- Plain Craft Launcher 2/Application.xaml.vb | 6 +- .../Controls/MyIconButton.xaml.vb | 2 +- .../Controls/MyListItem.xaml | 2 +- .../Controls/MyListItem.xaml.vb | 81 ++- .../Controls/MyLoading.xaml.vb | 92 +-- Plain Craft Launcher 2/FormMain.xaml.vb | 34 +- .../Images/Icons/Disabled.png | Bin 0 -> 701 bytes .../Images/Icons/NoIcon.png | Bin 3880 -> 3841 bytes .../Images/Icons/Unavaliable.png | Bin 0 -> 982 bytes .../Modules/Base/ModBase.vb | 30 +- .../Modules/Base/ModLoader.vb | 14 +- .../Modules/Minecraft/ModComp.vb | 565 ++++++++++-------- .../Modules/Minecraft/ModCrash.vb | 4 +- .../Modules/Minecraft/ModDownload.vb | 6 +- .../Modules/Minecraft/ModJava.vb | 3 + .../Modules/Minecraft/ModLaunch.vb | 9 +- .../Modules/Minecraft/ModMinecraft.vb | 10 +- .../Modules/Minecraft/ModMod.vb | 288 ++++++++- .../Modules/Minecraft/MyCompItem.xaml | 6 +- .../Modules/Minecraft/MyCompItem.xaml.vb | 115 +++- .../Modules/Minecraft/MyLocalModItem.xaml | 43 ++ .../Modules/Minecraft/MyLocalModItem.xaml.vb | 453 ++++++++++++++ Plain Craft Launcher 2/Modules/ModMusic.vb | 12 +- .../My Project/AssemblyInfo.vb | 4 +- .../Pages/PageDownload/ModDownloadLib.vb | 38 +- .../PageDownloadCompDetail.xaml.vb | 25 +- .../PageDownload/PageDownloadInstall.xaml | 2 +- .../Pages/PageDownload/PageDownloadLeft.xaml | 18 +- .../PageDownload/PageDownloadLeft.xaml.vb | 2 + .../Pages/PageDownload/PageDownloadMod.xaml | 2 + .../PageDownload/PageDownloadMod.xaml.vb | 7 + .../Pages/PageDownload/PageDownloadPack.xaml | 2 + .../PageDownload/PageDownloadPack.xaml.vb | 7 + .../Pages/PageLaunch/MySkin.xaml.vb | 3 +- .../Pages/PageLaunch/PageLaunchRight.xaml.vb | 4 + .../Pages/PageLink/PageLinkFeedback.xaml | 10 +- .../Pages/PageLink/PageLinkHiper.xaml.vb | 6 +- .../Pages/PageLink/PageLinkIoi.xaml.vb | 3 +- .../Pages/PageLink/PageLinkLeft.xaml | 12 +- .../Pages/PageOther/PageOtherLeft.xaml | 10 +- .../Pages/PageSelectLeft.xaml.vb | 2 +- .../Pages/PageSelectRight.xaml.vb | 2 +- .../Pages/PageSetup/PageSetupLeft.xaml | 16 +- .../Pages/PageVersion/PageVersionLeft.xaml | 10 +- .../Pages/PageVersion/PageVersionLeft.xaml.vb | 9 +- .../Pages/PageVersion/PageVersionMod.xaml | 28 +- .../Pages/PageVersion/PageVersionMod.xaml.vb | 324 +++------- .../Plain Craft Launcher 2.vbproj | 11 + Plain Craft Launcher 2/Resources/Help.zip | Bin 76824 -> 76255 bytes 50 files changed, 1552 insertions(+), 782 deletions(-) create mode 100644 Plain Craft Launcher 2/Images/Icons/Disabled.png create mode 100644 Plain Craft Launcher 2/Images/Icons/Unavaliable.png create mode 100644 Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml create mode 100644 Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb diff --git a/Plain Craft Launcher 2/Application.xaml b/Plain Craft Launcher 2/Application.xaml index 7a25967..c10b711 100644 --- a/Plain Craft Launcher 2/Application.xaml +++ b/Plain Craft Launcher 2/Application.xaml @@ -345,7 +345,7 @@ - + diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb index a0ffcef..8c8a4cc 100644 --- a/Plain Craft Launcher 2/Application.xaml.vb +++ b/Plain Craft Launcher 2/Application.xaml.vb @@ -116,9 +116,9 @@ Public Class Application ServicePointManager.Expect100Continue = True ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12 ServicePointManager.DefaultConnectionLimit = 1024 - ServicePointManager.ServerCertificateValidationCallback = New Security.RemoteCertificateValidationCallback(Function() As Boolean - Return True - End Function) + 'ServicePointManager.ServerCertificateValidationCallback = New Security.RemoteCertificateValidationCallback(Function() As Boolean + ' Return True + ' End Function) '计时 Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms") ApplicationStartTick = GetTimeTick() diff --git a/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb index d3ab372..5849df8 100644 --- a/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb @@ -133,7 +133,7 @@ Dim AnimList As New List(Of AniData) Select Case Theme Case Themes.Color - AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush5", AnimationColorOut)) + AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush4", AnimationColorOut)) PanBack.Background = New MyColor(0, 255, 255, 255) Case Themes.White AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush8", AnimationColorOut)) diff --git a/Plain Craft Launcher 2/Controls/MyListItem.xaml b/Plain Craft Launcher 2/Controls/MyListItem.xaml index 8bcf167..1624ab8 100644 --- a/Plain Craft Launcher 2/Controls/MyListItem.xaml +++ b/Plain Craft Launcher 2/Controls/MyListItem.xaml @@ -11,7 +11,7 @@ - + diff --git a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb index ea0e10f..cdaff6f 100644 --- a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb @@ -104,42 +104,34 @@ ColumnPaddingLeft.Width = New GridLength(value) End Set End Property - Public Property PaddingRight As Integer - Get - Return ColumnPaddingRight.Width.Value - End Get - Set(value As Integer) - ColumnPaddingRight.Width = New GridLength(value) - End Set - End Property + ''' + ''' 右边距的最小值。 + ''' 在存在右侧按钮时,右边距会被自动设置为 5 + 按钮数 * 25。 + ''' + Public Property MinPaddingRight As Integer = 4 '按钮 - Private _Buttons As IEnumerable - Public Property Buttons As IEnumerable + Private _Buttons As IEnumerable(Of MyIconButton) + Public Property Buttons As IEnumerable(Of MyIconButton) Get Return _Buttons End Get - Set(value As IEnumerable) + Set(value As IEnumerable(Of MyIconButton)) _Buttons = value '没有特殊按钮,移除原 Stack - If Not IsNothing(ButtonStack) Then + If ButtonStack IsNot Nothing Then Children.Remove(ButtonStack) ButtonStack = Nothing End If - '获取按钮数量 - Dim Count As Integer = 0 - For Each Counter In value - Count += 1 - Next '添加新 Stack - Select Case Count + Select Case value.Count Case 0 '没有按钮,不添加新的 Case 1 '只有一个按钮 For Each Btn As MyIconButton In value - If Btn.Height.Equals(Double.NaN) Then Btn.Height = 23 - If Btn.Width.Equals(Double.NaN) Then Btn.Width = 23 + If Btn.Height.Equals(Double.NaN) Then Btn.Height = 25 + If Btn.Width.Equals(Double.NaN) Then Btn.Width = 25 With Btn .Opacity = 0 .Margin = New Thickness(0, 0, 5, 0) @@ -149,22 +141,18 @@ .SnapsToDevicePixels = False .UseLayoutRounding = False End With - SetColumn(Btn, 4) - SetColumnSpan(Btn, 4) - SetRowSpan(Btn, 4) + SetColumnSpan(Btn, 10) : SetRowSpan(Btn, 10) Children.Add(Btn) ButtonStack = Btn Next Case Else '有复数按钮,使用 StackPanel ButtonStack = New StackPanel With {.Opacity = 0, .Margin = New Thickness(0, 0, 5, 0), .SnapsToDevicePixels = False, .Orientation = Orientation.Horizontal, .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Center, .UseLayoutRounding = False} - SetColumn(ButtonStack, 4) - SetColumnSpan(ButtonStack, 4) - SetRowSpan(ButtonStack, 4) + SetColumnSpan(ButtonStack, 10) : SetRowSpan(ButtonStack, 10) '构造按钮 For Each Btn As MyIconButton In value - If Btn.Height.Equals(Double.NaN) Then Btn.Height = 23 - If Btn.Width.Equals(Double.NaN) Then Btn.Width = 23 + If Btn.Height.Equals(Double.NaN) Then Btn.Height = 25 + If Btn.Width.Equals(Double.NaN) Then Btn.Width = 25 CType(ButtonStack, StackPanel).Children.Add(Btn) Next Children.Add(ButtonStack) @@ -324,7 +312,8 @@ '添加竖条控件 If IsNothing(RectCheck) Then RectCheck = New Border With {.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2), - .VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center), .HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False, + .VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center), + .HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False, .Margin = If(Checked, New Thickness(-1, 6, 0, 6), New Thickness(-1, 0, 0, 0))} RectCheck.SetResourceReference(Border.BackgroundProperty, "ColorBrush3") SetRowSpan(RectCheck, 4) @@ -532,10 +521,6 @@ Log("[Control] 按下复选列表项(" & (Not Checked).ToString & "):" & Title) SetChecked(Not Checked, True, True) End Select - 'AniStart({ - ' AaScaleTransform(Me, 1.004 - CType(RenderTransform, ScaleTransform).ScaleX, 150,, New AniEaseOutFluent), - ' AaScaleTransform(Me, -0.004, 100, 150, New AniEaseInFluent) - ' }, "MyListItem Scale " & Uuid) End Sub '鼠标点击判定 @@ -543,15 +528,12 @@ Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown If IsMouseDirectlyOver AndAlso Not Type = CheckType.None Then IsMouseDown = True - If Not IsNothing(ButtonStack) Then ButtonStack.IsHitTestVisible = False + If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = False End If - 'AniStart({ - ' AaScaleTransform(Me, 0.985 - CType(RenderTransform, ScaleTransform).ScaleX, 50,, New AniEaseOutFluent) - ' }, "MyListItem Scale " & Uuid) End Sub Private Sub Button_MouseLeave(sender As Object, e As Object) Handles Me.MouseLeave, Me.PreviewMouseLeftButtonUp IsMouseDown = False - If Not IsNothing(ButtonStack) Then ButtonStack.IsHitTestVisible = True + If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True End Sub '实现自定义事件 @@ -605,7 +587,11 @@ '有动画 Dim Ani As New List(Of AniData) If IsMouseOver AndAlso IsMouseOverAnimationEnabled Then - If ButtonStack IsNot Nothing Then Ani.Add(AaOpacity(ButtonStack, 1 - ButtonStack.Opacity, Time * 0.7, Time * 0.3)) + If ButtonStack IsNot Nothing Then + Ani.Add(AaOpacity(ButtonStack, 1 - ButtonStack.Opacity, Time * 0.7, Time * 0.3)) + Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)), + Math.Max(MinPaddingRight, 5 + Buttons.Count * 25) - ColumnPaddingRight.Width.Value, Time * 0.3, Time * 0.7)) + End If Ani.AddRange({ AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrushBg1"), Time), AaOpacity(RectBack, 1 - RectBack.Opacity, Time,, New AniEaseOutFluent) @@ -619,7 +605,11 @@ End If End If Else - If ButtonStack IsNot Nothing Then Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.5)) + If ButtonStack IsNot Nothing Then + Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.4)) + Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)), + MinPaddingRight - ColumnPaddingRight.Width.Value, Time * 0.4)) + End If Ani.Add(AaOpacity(RectBack, -RectBack.Opacity, Time)) If IsScaleAnimationEnabled Then Ani.AddRange({ @@ -634,14 +624,20 @@ Else '无动画 If IsMouseOver AndAlso IsMouseOverAnimationEnabled Then - If ButtonStack IsNot Nothing Then ButtonStack.Opacity = 1 + If ButtonStack IsNot Nothing Then + ButtonStack.Opacity = 1 + ColumnPaddingRight.Width = New GridLength(Math.Max(MinPaddingRight, 5 + Buttons.Count * 25)) + End If '由于鼠标已经移入,所以直接实例化 RectBack RectBack.Background = ColorBg1 RectBack.Opacity = 1 RectBack.RenderTransform = New ScaleTransform(1, 1) Me.RenderTransform = New ScaleTransform(1, 1) Else - If ButtonStack IsNot Nothing Then ButtonStack.Opacity = 0 + If ButtonStack IsNot Nothing Then + ButtonStack.Opacity = 0 + ColumnPaddingRight.Width = New GridLength(MinPaddingRight) + End If Me.RenderTransform = New ScaleTransform(1, 1) If _RectBack IsNot Nothing Then If IsScaleAnimationEnabled Then RectBack.RenderTransform = New ScaleTransform(0.75, 0.75) @@ -659,6 +655,7 @@ Else SetResourceReference(ForegroundProperty, "ColorBrush1") End If + ColumnPaddingRight.Width = New GridLength(MinPaddingRight) If EventType = "打开帮助" Then Try Dim NewElem = New HelpEntry(GetEventAbsoluteUrls(EventData, EventType)(0)).SetToListItem(Me) diff --git a/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb b/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb index 0fab85b..77e0161 100644 --- a/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb @@ -67,32 +67,35 @@ Public Class MyLoading ''' Public Property TextErrorInherit As Boolean = True - Private Sub RefreshText() Handles Me.IsErrorChanged, Me.Loaded - If InnerState = MyLoadingState.Error Then - If TextErrorInherit AndAlso State.IsLoader Then - Dim Ex As Exception = CType(State, Object).Error - If Ex Is Nothing Then - LabText.Text = "未知错误" - Else - Do While Ex.InnerException IsNot Nothing - Ex = Ex.InnerException - Loop - LabText.Text = StrTrim(Ex.Message) - If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", - "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then - LabText.Text = "网络环境不佳,请重试或尝试使用 VPN" + Private Sub RefreshText() Handles Me.IsErrorChanged, Me.Loaded, _State.ProgressChanged + RunInUi( + Sub() + If InnerState = MyLoadingState.Error Then + If TextErrorInherit AndAlso State.IsLoader Then + Dim Ex As Exception = CType(State, Object).Error + If Ex Is Nothing Then + LabText.Text = "未知错误" + Else + Do While Ex.InnerException IsNot Nothing + Ex = Ex.InnerException + Loop + LabText.Text = StrTrim(Ex.Message) + If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", + "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then + LabText.Text = "网络环境不佳,请重试或尝试使用 VPN" + End If End If + Else + LabText.Text = TextError End If Else - LabText.Text = TextError + If ShowProgress AndAlso State.IsLoader Then + LabText.Text = Text & " - " & Math.Floor(CType(State, Object).Progress * 100) & "%" + Else + LabText.Text = Text + End If End If - Else - If ShowProgress AndAlso State.IsLoader Then - LabText.Text = Text & " - " & Math.Floor(CType(State, Object).Progress * 100) & "%" - Else - LabText.Text = Text - End If - End If + End Sub) End Sub #End Region @@ -183,46 +186,7 @@ Public Class MyLoading If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Exit Sub IsLooping = True ErrorAnimationWaiting = True - If ShowProgress Then - RefreshText() - AniStart({ - AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)), - AaCode(AddressOf RefreshText, 100), '这是为了省下一个 Timer 线程…… - AaCode(AddressOf RefreshText, 200), - AaCode(AddressOf RefreshText, 300), - AaCode(AddressOf RefreshText, 400), - AaCode(AddressOf RefreshText, 500), - AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True), - AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)), - AaCode(Sub() - PathLeft.Opacity = 1 - PathLeft.Margin = New Thickness(7, 41, 0, 0) - PathRight.Opacity = 1 - PathRight.Margin = New Thickness(14, 41, 0, 0) - ErrorAnimationWaiting = False - RefreshText() - End Sub), - AaCode(AddressOf RefreshText, 100), - AaCode(AddressOf RefreshText, 200), - AaCode(AddressOf RefreshText, 300), - AaCode(AddressOf RefreshText, 400), - AaCode(AddressOf RefreshText, 500), - AaCode(AddressOf RefreshText, 600), - AaCode(AddressOf RefreshText, 700), - AaCode(AddressOf RefreshText, 800), - AaOpacity(PathLeft, -1, 100, 70), - AaX(PathLeft, -5, 180, 20, New AniEaseOutFluent), - AaY(PathLeft, -6, 180, 20, New AniEaseOutFluent), - AaOpacity(PathRight, -1, 100, 70), - AaX(PathRight, 5, 180, 20, New AniEaseOutFluent), - AaY(PathRight, -6, 180, 20, New AniEaseOutFluent), - AaCode(Sub() - IsLooping = False - AniLoop() - End Sub,, True) - }, "MyLoader Loop " & Uuid & "/" & GetUuid()) - Else - AniStart({ + AniStart({ AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)), AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True), AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)), @@ -244,6 +208,8 @@ Public Class MyLoading AniLoop() End Sub,, True) }, "MyLoader Loop " & Uuid & "/" & GetUuid()) + If ShowProgress Then + End If End Sub @@ -294,6 +260,7 @@ Public Interface ILoadingTrigger ReadOnly Property IsLoader As Boolean Property LoadingState As MyLoadingState Event LoadingStateChanged(NewState As MyLoadingState, OldState As MyLoadingState) + Event ProgressChanged(NewProgress As Double, OldProgress As Double) End Interface Public Class MyLoadingStateSimulator @@ -313,4 +280,5 @@ Public Class MyLoadingStateSimulator Public ReadOnly Property IsLoader As Boolean = False Implements ILoadingTrigger.IsLoader Public Event LoadingStateChanged(NewState As MyLoadingState, OldState As MyLoadingState) Implements ILoadingTrigger.LoadingStateChanged + Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged End Class diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index 356a60e..cfcff52 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -1,4 +1,5 @@ Imports System.ComponentModel +Imports System.Runtime.InteropServices Public Class FormMain @@ -134,6 +135,13 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- + If LastVersion < 307 Then 'Snapshot 2.6.11 + FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 管理页面将显示 Mod 的中文名、图标、标签等信息")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "从 Mod 管理页面查看 Mod 信息时会跳转到其下载详情页面")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新设计 Mod 管理页面的交互与样式")) + FeatureCount += 15 + BugCount += 13 + End If If LastVersion < 307 Then 'Snapshot 2.6.10 FeatureCount += 2 BugCount += 3 @@ -351,7 +359,6 @@ Public Class FormMain End Sub '窗口加载 - Private Declare Auto Function ChangeWindowMessageFilterEx Lib "user32.dll" (ByVal hwnd As IntPtr, ByVal message As UInt32, ByVal action As UInt32, ByVal changeInfo As UInt32) As Boolean Private IsWindowLoadFinished As Boolean = False ''' ''' 是否为联机提权后自动重启。 @@ -489,20 +496,25 @@ Public Class FormMain Catch ex As Exception Log(ex, "清理自动更新文件失败") End Try - '开启管理员权限下的文件拖拽(#2531) - Try - If IsAdmin() Then - Log("[System] PCL 正以管理员权限运行") - ChangeWindowMessageFilterEx(Handle, &H233, 1, Nothing) - ChangeWindowMessageFilterEx(Handle, &H49, 1, Nothing) - End If - Catch ex As Exception - Log(ex, "开启管理员权限下的文件拖拽失败") - End Try + '开启管理员权限下的文件拖拽,但下列代码也没用(#2531) + If IsAdmin() Then + Log("[System] PCL 正以管理员权限运行") + Dim changeInfo As New ChangeFilter + changeInfo.cbSize = Marshal.SizeOf(changeInfo) + ChangeWindowMessageFilterEx(Handle, &H233, 1, changeInfo) + ChangeWindowMessageFilterEx(Handle, &H4A, 1, changeInfo) + ChangeWindowMessageFilterEx(Handle, &H49, 1, changeInfo) + End If End Sub, "Start Loader", ThreadPriority.Lowest) Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms") End Sub + + Public Structure ChangeFilter + Public cbSize As UInteger + Public ExtStatus As UInteger + End Structure + Private Declare Function ChangeWindowMessageFilterEx Lib "user32.dll" (hwnd As IntPtr, message As UInteger, action As UInteger, changeInfo As ChangeFilter) As Boolean '根据打开次数触发的事件 Private Sub RunCountSub() Setup.Set("SystemCount", Setup.Get("SystemCount") + 1) diff --git a/Plain Craft Launcher 2/Images/Icons/Disabled.png b/Plain Craft Launcher 2/Images/Icons/Disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..b335561190529640721007e14a806dec60d50319 GIT binary patch literal 701 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 Ef<@!IoB#j- literal 0 HcmV?d00001 diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index 69d3eae..f84b474 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.100" '不含分支前缀的显示用版本名 - Public Const VersionStandardCode As String = "2.6.100." & VersionBranchCode '标准格式的四段式版本号 + Public Const VersionBaseName As String = "2.6.11" '不含分支前缀的显示用版本名 + Public Const VersionStandardCode As String = "2.6.11." & VersionBranchCode '标准格式的四段式版本号 #If BETA Then Public Const VersionCode As Integer = 308 'Release #Else - Public Const VersionCode As Integer = 307 'Snapshot + Public Const VersionCode As Integer = 309 'Snapshot #End If '自动生成的版本信息 Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName @@ -560,7 +560,7 @@ Public Module ModBase ''' ''' 重命名一个注册表子键。不可用于包含子键的子键。 ''' - Public Sub RegRename(parentKey As Microsoft.Win32.RegistryKey, subKeyName As String, newSubKeyName As String) + Public Sub RenameReg(parentKey As Microsoft.Win32.RegistryKey, subKeyName As String, newSubKeyName As String) If parentKey.GetSubKeyNames().Contains(newSubKeyName) Then parentKey.DeleteSubKeyTree(newSubKeyName, False) Dim SourceKey As Microsoft.Win32.RegistryKey = parentKey.OpenSubKey(subKeyName) If IsNothing(SourceKey) Then Exit Sub '没有目标项 @@ -1062,7 +1062,7 @@ Public Module ModBase ''' ''' 获取文件 MD5,若失败则返回空字符串。 ''' - Public Function GetAuthMD5(FilePath As String) As String + Public Function GetFileMD5(FilePath As String) As String Dim Retry As Boolean = False Re: Try @@ -1091,7 +1091,7 @@ Re: ''' ''' 获取文件 SHA256,若失败则返回空字符串。 ''' - Public Function GetAuthSHA256(FilePath As String) As String + Public Function GetFileSHA256(FilePath As String) As String Dim Retry As Boolean = False Re: Try @@ -1122,7 +1122,7 @@ Re: ''' ''' 获取文件 SHA1,若失败则返回空字符串。 ''' - Public Function GetAuthSHA1(FilePath As String) As String + Public Function GetFileSHA1(FilePath As String) As String Dim Retry As Boolean = False Re: Try @@ -1210,11 +1210,11 @@ Re: If MinSize >= 0 AndAlso MinSize > FileSize Then Return "文件大小应大于 " & MinSize & " B,实际为 " & FileSize & " B" If Not String.IsNullOrEmpty(Hash) Then If Hash.Length < 35 Then 'MD5 - If Hash.ToLowerInvariant <> GetAuthMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetAuthMD5(LocalPath) + If Hash.ToLowerInvariant <> GetFileMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetFileMD5(LocalPath) ElseIf Hash.Length = 64 Then 'SHA256 - If Hash.ToLowerInvariant <> GetAuthSHA256(LocalPath) Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & GetAuthSHA256(LocalPath) + If Hash.ToLowerInvariant <> GetFileSHA256(LocalPath) Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & GetFileSHA256(LocalPath) Else 'SHA1 (40) - If Hash.ToLowerInvariant <> GetAuthSHA1(LocalPath) Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & GetAuthSHA1(LocalPath) + If Hash.ToLowerInvariant <> GetFileSHA1(LocalPath) Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & GetFileSHA1(LocalPath) End If End If If IsJson Then @@ -2321,6 +2321,16 @@ Retry: Return Thread.CurrentThread.ManagedThreadId = UiThreadId End Function + ''' + ''' 检查某个控件是否位于主窗口可视区域内,且控件本身可见。 + ''' + Public Function IsVisibleInForm(element As FrameworkElement) As Boolean + If Not element.IsVisible Then Return False + Dim bounds As Rect = element.TransformToAncestor(FrmMain).TransformBounds(New Rect(0, 0, element.ActualWidth, element.ActualHeight)) + Dim rect As New Rect(0, 0, FrmMain.ActualWidth, FrmMain.ActualHeight) + Return rect.Contains(bounds.TopLeft) OrElse rect.Contains(bounds.BottomRight) + End Function + #End Region #Region "Debug" diff --git a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb index 6a173c1..852541d 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb @@ -153,10 +153,14 @@ End Select End Get Set(value As Double) + If _Progress = value Then Exit Property + Dim OldValue = _Progress _Progress = value + RaiseEvent ProgressChanged(value, OldValue) End Set End Property Private _Progress As Double = -1 + Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged ''' ''' 计算总进度时的权重。它应该为预计时间(秒)。 ''' @@ -236,12 +240,16 @@ ''' Public ReadOnly Property IsAborted As Boolean Get - Return LastRunningThread Is Nothing OrElse - Not ReferenceEquals(Thread.CurrentThread, LastRunningThread) OrElse - State = LoadState.Aborted + Return IsAbortedWithThread(Thread.CurrentThread) End Get End Property ''' + ''' 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。 + ''' + Public Function IsAbortedWithThread(Thread As Thread) As Boolean + Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Aborted + End Function + ''' ''' 在输入相同时使用原有结果的超时,单位为毫秒。 ''' Public ReloadTimeout As Integer = -1 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb index c4fb77a..dcefb92 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb @@ -4,15 +4,15 @@ ''' ''' Mod。 ''' - [Mod] + [Mod] = 0 ''' ''' 整合包。 ''' - ModPack + ModPack = 1 ''' ''' 资源包。 ''' - ResourcePack + ResourcePack = 2 End Enum Public Enum CompModLoaderType 'https://docs.curseforge.com/?http#tocS_ModLoaderType @@ -164,7 +164,7 @@ ''' ''' Logo 图片的下载地址。若为 Nothing 则没有。 ''' - Public ReadOnly LogoUrl As String = Nothing + Public LogoUrl As String = Nothing ''' ''' 游戏大版本列表。例如:18, 16, 15…… ''' @@ -221,174 +221,224 @@ ''' 从工程 Json 中初始化实例。若出错会抛出异常。 ''' Public Sub New(Data As JObject) - FromCurseForge = Data.ContainsKey("gameId") - If FromCurseForge Then -#Region "CurseForge" - '简单信息 - Id = Data("id") - Slug = Data("slug") - RawName = Data("name") - Description = Data("summary") - Website = Data("links")("websiteUrl").ToString.TrimEnd("/") - LastUpdate = Data("dateReleased") '#1194 - DownloadCount = Data("downloadCount") - 'Logo - If Data("logo").Count > 0 Then - If GetPixelSize(1) > 1.25 Then - LogoUrl = Data("logo")("thumbnailUrl").ToString '使用 256x256 图标 - Else - LogoUrl = Data("logo")("thumbnailUrl").ToString.Replace("/256/256/", "/64/64/") '使用 64x64 图标 - End If - End If - 'FileIndexes / GameVersions - Dim Files = If(CType(Data("latestFilesIndexes"), JArray), New JArray). - Where(Function(i) i("gameVersion").ToString.Contains("1.")) - CurseForgeFileIds = Files.Select(Of Integer)(Function(v) v("fileId")).ToList - GameVersions = Files.Select(Of Integer)(Function(v) Val(v("gameVersion").ToString.Split(".")(1).Split("-").First)).Where(Function(v) v > 0). - Distinct.OrderByDescending(Function(v) v).ToList - 'Type - If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then - Type = CompType.Mod - ElseIf Website.Contains("/modpacks/") Then - Type = CompType.ModPack + If Data.ContainsKey("Tags") Then +#Region "CompJson" + FromCurseForge = Data("DataSource") = "CurseForge" + Type = Data("Type").ToObject(Of Integer) + Slug = Data("Slug") + Id = Data("Id") + If Data.ContainsKey("CurseForgeFileIds") Then CurseForgeFileIds = CType(Data("CurseForgeFileIds"), JArray).Select(Function(t) t.ToObject(Of Integer)).ToList + RawName = Data("RawName") + Description = Data("Description") + Website = Data("Website") + If Data.ContainsKey("LastUpdate") Then LastUpdate = Data("LastUpdate") + DownloadCount = Data("DownloadCount") + If Data.ContainsKey("ModLoaders") Then + ModLoaders = CType(Data("ModLoaders"), JArray).Select(Function(t) CType(t.ToObject(Of Integer), CompModLoaderType)).ToList Else - Type = CompType.ResourcePack + ModLoaders = New List(Of CompModLoaderType) + End If + Tags = CType(Data("Tags"), JArray).Select(Function(t) t.ToString).ToList + If Data.ContainsKey("LogoUrl") Then LogoUrl = Data("LogoUrl") + If Data.ContainsKey("GameVersions") Then + GameVersions = CType(Data("GameVersions"), JArray).Select(Function(t) t.ToObject(Of Integer)).ToList + Else + GameVersions = New List(Of Integer) End If - 'Tags - Tags = New List(Of String) - For Each Category In Data("categories").Select(Of Integer)(Function(t) t("id")).Distinct.OrderByDescending(Function(c) c) - Select Case Category - 'Mod - Case 406 : Tags.Add("世界元素") - Case 407 : Tags.Add("生物群系") - Case 410 : Tags.Add("维度") - Case 408 : Tags.Add("矿物/资源") - Case 409 : Tags.Add("天然结构") - Case 412 : Tags.Add("科技") - Case 415 : Tags.Add("管道/物流") - Case 4843 : Tags.Add("自动化") - Case 417 : Tags.Add("能源") - Case 4558 : Tags.Add("红石") - Case 436 : Tags.Add("食物/烹饪") - Case 416 : Tags.Add("农业") - Case 414 : Tags.Add("运输") - Case 420 : Tags.Add("仓储") - Case 419 : Tags.Add("魔法") - Case 422 : Tags.Add("冒险") - Case 424 : Tags.Add("装饰") - Case 411 : Tags.Add("生物") - Case 434 : Tags.Add("装备") - Case 423 : Tags.Add("信息显示") - Case 435 : Tags.Add("服务器") - Case 5191 : Tags.Add("改良") - Case 421 : Tags.Add("支持库") - '整合包 - Case 4484 : Tags.Add("多人") - Case 4479 : Tags.Add("硬核") - Case 4483 : Tags.Add("战斗") - Case 4478 : Tags.Add("任务") - Case 4472 : Tags.Add("科技") - Case 4473 : Tags.Add("魔法") - Case 4475 : Tags.Add("冒险") - Case 4476 : Tags.Add("探索") - Case 4477 : Tags.Add("小游戏") - Case 4471 : Tags.Add("科幻") - Case 4736 : Tags.Add("空岛") - Case 5128 : Tags.Add("原版改良") - Case 4487 : Tags.Add("FTB") - Case 4480 : Tags.Add("基于地图") - Case 4481 : Tags.Add("轻量") - Case 4482 : Tags.Add("大型") - 'FUTURE: Res - End Select - Next - If Tags.Count = 0 Then Tags.Add("杂项") - 'ModLoaders - ModLoaders = New List(Of CompModLoaderType) - For Each File In If(Data("latestFiles"), {}) - Dim NewFile As New CompFile(File, Type) - If Not NewFile.Available Then Continue For - ModLoaders.AddRange(NewFile.ModLoaders) - Next - ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList #End Region Else -#Region "Modrinth" - '简单信息 - Id = If(Data("project_id"), Data("id")) '两个 API 会返回的 key 不一样 - Slug = Data("slug") - RawName = Data("title") - Description = Data("description") - LastUpdate = Data("date_modified") - DownloadCount = Data("downloads") - LogoUrl = Data("icon_url") - If LogoUrl = "" Then LogoUrl = Nothing - Website = $"https://modrinth.com/{Data("project_type")}/{Slug}" - 'GameVersions - GameVersions = If(CType(Data("versions"), JArray), New JArray). - Select(Function(v) v.ToString).Where(Function(v) v.StartsWith("1.")). - Select(Of Integer)(Function(v) Val(v.Split(".")(1).Split("-").First)).Where(Function(v) v > 0). + FromCurseForge = Data.ContainsKey("summary") + If FromCurseForge Then +#Region "CurseForge" + '简单信息 + Id = Data("id") + Slug = Data("slug") + RawName = Data("name") + Description = Data("summary") + Website = Data("links")("websiteUrl").ToString.TrimEnd("/") + LastUpdate = Data("dateReleased") '#1194 + DownloadCount = Data("downloadCount") + 'Logo + If Data("logo").Count > 0 Then + If GetPixelSize(1) > 1.25 Then + LogoUrl = Data("logo")("thumbnailUrl").ToString '使用 256x256 图标 + Else + LogoUrl = Data("logo")("thumbnailUrl").ToString.Replace("/256/256/", "/64/64/") '使用 64x64 图标 + End If + End If + 'FileIndexes / GameVersions + Dim Files = If(CType(Data("latestFilesIndexes"), JArray), New JArray). + Where(Function(i) i("gameVersion").ToString.Contains("1.")) + CurseForgeFileIds = Files.Select(Of Integer)(Function(v) v("fileId")).Distinct.ToList + GameVersions = Files.Select(Of Integer)(Function(v) Val(v("gameVersion").ToString.Split(".")(1).Split("-").First)).Where(Function(v) v > 0). Distinct.OrderByDescending(Function(v) v).ToList - 'Type - Select Case Data("project_type").ToString - Case "mod" : Type = CompType.Mod - Case "modpack" : Type = CompType.ModPack - Case "resourcepack" : Type = CompType.ResourcePack - End Select - 'Tags & ModLoaders - Tags = New List(Of String) - ModLoaders = New List(Of CompModLoaderType) - For Each Category In Data("categories").Select(Function(t) t.ToString) - Select Case Category - '加载器 - Case "forge" : ModLoaders.Add(CompModLoaderType.Forge) - Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric) - Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt) + 'Type + If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then + Type = CompType.Mod + ElseIf Website.Contains("/modpacks/") Then + Type = CompType.ModPack + Else + Type = CompType.ResourcePack + End If + 'Tags + Tags = New List(Of String) + For Each Category In Data("categories").Select(Of Integer)(Function(t) t("id")).Distinct.OrderByDescending(Function(c) c) + Select Case Category 'Mod - Case "worldgen" : Tags.Add("世界元素") - Case "technology" : Tags.Add("科技") - Case "food" : Tags.Add("食物/烹饪") - Case "game-mechanics" : Tags.Add("游戏机制") - Case "transportation" : Tags.Add("运输") - Case "storage" : Tags.Add("仓储") - Case "magic" : Tags.Add("魔法") - Case "adventure" : Tags.Add("冒险") - Case "decoration" : Tags.Add("装饰") - Case "mobs" : Tags.Add("生物") - Case "equipment" : Tags.Add("装备") - Case "optimization" : Tags.Add("性能优化") - Case "social" : Tags.Add("服务器") - Case "utility" : Tags.Add("改良") - Case "library" : Tags.Add("支持库") + Case 406 : Tags.Add("世界元素") + Case 407 : Tags.Add("生物群系") + Case 410 : Tags.Add("维度") + Case 408 : Tags.Add("矿物/资源") + Case 409 : Tags.Add("天然结构") + Case 412 : Tags.Add("科技") + Case 415 : Tags.Add("管道/物流") + Case 4843 : Tags.Add("自动化") + Case 417 : Tags.Add("能源") + Case 4558 : Tags.Add("红石") + Case 436 : Tags.Add("食物/烹饪") + Case 416 : Tags.Add("农业") + Case 414 : Tags.Add("运输") + Case 420 : Tags.Add("仓储") + Case 419 : Tags.Add("魔法") + Case 422 : Tags.Add("冒险") + Case 424 : Tags.Add("装饰") + Case 411 : Tags.Add("生物") + Case 434 : Tags.Add("装备") + Case 423 : Tags.Add("信息显示") + Case 435 : Tags.Add("服务器") + Case 5191 : Tags.Add("改良") + Case 421 : Tags.Add("支持库") '整合包 - Case "multiplayer" : Tags.Add("多人") - Case "optimization" : Tags.Add("性能优化") - Case "challenging" : Tags.Add("硬核") - Case "combat" : Tags.Add("战斗") - Case "quests" : Tags.Add("任务") - Case "technology" : Tags.Add("科技") - Case "magic" : Tags.Add("魔法") - Case "adventure" : Tags.Add("冒险") - Case "kitchen-sink" : Tags.Add("烹饪") - Case "lightweight" : Tags.Add("轻量") - 'FUTURE: Res - End Select - Next - If Tags.Count = 0 Then Tags.Add("杂项") - Tags.Sort() - ModLoaders.Sort() + Case 4484 : Tags.Add("多人") + Case 4479 : Tags.Add("硬核") + Case 4483 : Tags.Add("战斗") + Case 4478 : Tags.Add("任务") + Case 4472 : Tags.Add("科技") + Case 4473 : Tags.Add("魔法") + Case 4475 : Tags.Add("冒险") + Case 4476 : Tags.Add("探索") + Case 4477 : Tags.Add("小游戏") + Case 4471 : Tags.Add("科幻") + Case 4736 : Tags.Add("空岛") + Case 5128 : Tags.Add("原版改良") + Case 4487 : Tags.Add("FTB") + Case 4480 : Tags.Add("基于地图") + Case 4481 : Tags.Add("轻量") + Case 4482 : Tags.Add("大型") + 'FUTURE: Res + End Select + Next + If Tags.Count = 0 Then Tags.Add("杂项") + 'ModLoaders + ModLoaders = New List(Of CompModLoaderType) + For Each File In If(Data("latestFiles"), {}) + Dim NewFile As New CompFile(File, Type) + If Not NewFile.Available Then Continue For + ModLoaders.AddRange(NewFile.ModLoaders) + Next + ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList #End Region + Else +#Region "Modrinth" + '简单信息 + Id = If(Data("project_id"), Data("id")) '两个 API 会返回的 key 不一样 + Slug = Data("slug") + RawName = Data("title") + Description = Data("description") + LastUpdate = Data("date_modified") + DownloadCount = Data("downloads") + LogoUrl = Data("icon_url") + If LogoUrl = "" Then LogoUrl = Nothing + Website = $"https://modrinth.com/{Data("project_type")}/{Slug}" + 'GameVersions + '搜索结果的键为 versions,获取特定工程的键为 game_versions + GameVersions = If(CType(If(Data("game_versions"), Data("versions")), JArray), New JArray). + Select(Function(v) v.ToString).Where(Function(v) v.StartsWith("1.")). + Select(Of Integer)(Function(v) Val(v.Split(".")(1).Split("-").First)).Where(Function(v) v > 0). + Distinct.OrderByDescending(Function(v) v).ToList + 'Type + Select Case Data("project_type").ToString + Case "mod" : Type = CompType.Mod + Case "modpack" : Type = CompType.ModPack + Case "resourcepack" : Type = CompType.ResourcePack + End Select + 'Tags & ModLoaders + Tags = New List(Of String) + ModLoaders = New List(Of CompModLoaderType) + For Each Category In Data("categories").Select(Function(t) t.ToString) + Select Case Category + '加载器 + Case "forge" : ModLoaders.Add(CompModLoaderType.Forge) + Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric) + Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt) + 'Mod + Case "worldgen" : Tags.Add("世界元素") + Case "technology" : Tags.Add("科技") + Case "food" : Tags.Add("食物/烹饪") + Case "game-mechanics" : Tags.Add("游戏机制") + Case "transportation" : Tags.Add("运输") + Case "storage" : Tags.Add("仓储") + Case "magic" : Tags.Add("魔法") + Case "adventure" : Tags.Add("冒险") + Case "decoration" : Tags.Add("装饰") + Case "mobs" : Tags.Add("生物") + Case "equipment" : Tags.Add("装备") + Case "optimization" : Tags.Add("性能优化") + Case "social" : Tags.Add("服务器") + Case "utility" : Tags.Add("改良") + Case "library" : Tags.Add("支持库") + '整合包 + Case "multiplayer" : Tags.Add("多人") + Case "optimization" : Tags.Add("性能优化") + Case "challenging" : Tags.Add("硬核") + Case "combat" : Tags.Add("战斗") + Case "quests" : Tags.Add("任务") + Case "technology" : Tags.Add("科技") + Case "magic" : Tags.Add("魔法") + Case "adventure" : Tags.Add("冒险") + Case "kitchen-sink" : Tags.Add("烹饪") + Case "lightweight" : Tags.Add("轻量") + 'FUTURE: Res + End Select + Next + If Tags.Count = 0 Then Tags.Add("杂项") + Tags.Sort() + ModLoaders.Sort() +#End Region + End If End If '保存缓存 CompProjectCache(Id) = Me End Sub ''' + ''' 将当前实例转为可用于保存缓存的 Json。 + ''' + Public Function ToJson() As JObject + Dim Json As New JObject + Json("DataSource") = If(FromCurseForge, "CurseForge", "Modrinth") + Json("Type") = CInt(Type) + Json("Slug") = Slug + Json("Id") = Id + If CurseForgeFileIds IsNot Nothing Then Json("CurseForgeFileIds") = New JArray(CurseForgeFileIds) + Json("RawName") = RawName + Json("Description") = Description + Json("Website") = Website + If LastUpdate IsNot Nothing Then Json("LastUpdate") = LastUpdate + Json("DownloadCount") = DownloadCount + If ModLoaders IsNot Nothing AndAlso ModLoaders.Count > 0 Then Json("ModLoaders") = New JArray(ModLoaders.Select(Function(m) CInt(m))) + Json("Tags") = New JArray(Tags) + If LogoUrl IsNot Nothing Then Json("LogoUrl") = LogoUrl + If GameVersions.Count > 0 Then Json("GameVersions") = New JArray(GameVersions) + Json("CacheTime") = Date.Now '用于检查缓存时间 + Return Json + End Function + ''' ''' 将当前工程信息实例化为控件。 ''' Public Function ToCompItem(ShowMcVersionDesc As Boolean, ShowLoaderDesc As Boolean) As MyCompItem '获取版本描述 Dim GameVersionDescription As String - If GameVersions Is Nothing OrElse GameVersions.Count = 0 Then + If GameVersions Is Nothing OrElse Not GameVersions.Any() Then GameVersionDescription = "未知" Else Dim SpaVersions As New List(Of String) @@ -418,94 +468,16 @@ GameVersionDescription = SpaVersions.Join(", ") End If '实例化 UI - Dim NewItem As New MyCompItem With {.Tag = Me} - '整理 FinalName 与 ExNameList - '检查下列代码时可以参考 #1567 的测试例 - Dim FinalName As String = RawName - Dim ExNameList As List(Of String) - If TranslatedName = RawName Then - '没有中文翻译 - '将所有名称分段 - 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 AndAlso Name <> "FPS" AndAlso Name <> "HUD" 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 - '检查时注意 Carpet:它没有中文译名,但有 Suffix - FinalName = TranslatedName.Split(" (").First.Split(" - ").First - Dim Suffix As String = "" - If TranslatedName.Split(")").Last.Contains(" - ") Then Suffix = TranslatedName.Split(")").Last.Split(" - ").Last - Dim EnglishName As String = TranslatedName - If Suffix <> "" Then EnglishName = EnglishName.Replace(" - " & Suffix, "") - EnglishName = EnglishName.Replace(FinalName, "").Trim("("c, ")"c, " "c) - '中段的额外信息截取 - ExNameList = EnglishName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries). - Select(Function(s) s.Trim(" /".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList - If ExNameList.Count > 1 AndAlso - Not ExNameList.Any(Function(s) s.ToLower.Contains("forge") OrElse s.ToLower.Contains("fabric") OrElse s.ToLower.Contains("quilt")) AndAlso '不是标注 XX 版 - Not (ExNameList.Count = 2 AndAlso ExNameList.Last.ToUpper = ExNameList.Last) Then '不是缩写 - ExNameList = New List(Of String) From {EnglishName} '使用原名 - End If - '添加后缀 - If Suffix <> "" Then ExNameList.Add(Suffix) - End If - ExNameList = ExNameList.Distinct().ToList() - '显示 FinalName 与 ExNameList - If ExNameList.Count = 0 Then -NoExName: - NewItem.LabTitle.Text = FinalName + Dim NewItem As New MyCompItem With {.Tag = Me, .Logo = GetControlLogo()} + Dim Title = GetControlTitle(True) + NewItem.Title = Title.Key + If Title.Value = "" Then CType(NewItem.LabTitleRaw.Parent, StackPanel).Children.Remove(NewItem.LabTitleRaw) Else - NewItem.LabTitle.Text = FinalName - Dim ExName As String = "" - For Each Ex In ExNameList - '去除 “Forge/Fabric” 这一无意义提示 - 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 - Ex = Ex.Replace("Edition", "").Replace("edition", "").Trim.Capitalize & " 版" - End If - '将 “forge” 等词语的首字母大写 - Ex = Ex.Replace("forge", "Forge").Replace("neo", "Neo").Replace("fabric", "Fabric").Replace("quilt", "Quilt") - ExName &= " | " & Ex.Trim - Next - If ExName = "" Then GoTo NoExName - NewItem.LabTitleRaw.Text = ExName + NewItem.SubTitle = Title.Value End If - 'Tag - For Each TagText In Tags - Dim NewTag = GetObjectFromXML( - " - - ") - NewItem.PanTags.Children.Add(NewTag) - Next + NewItem.Tags = Tags + NewItem.Description = Description.Replace(vbCr, "").Replace(vbLf, "") '下边栏 If Not ShowMcVersionDesc AndAlso Not ShowLoaderDesc Then '全部隐藏 @@ -548,7 +520,6 @@ NoExName: End If End Select End If - NewItem.LabInfo.Text = Description.Replace(vbCr, "").Replace(vbLf, "") NewItem.LabSource.Text = If(FromCurseForge, "CurseForge", "Modrinth") If LastUpdate IsNot Nothing Then NewItem.LabTime.Text = GetTimeSpanString(LastUpdate - Date.Now, True) @@ -561,14 +532,93 @@ NoExName: NewItem.LabDownload.Text = If(DownloadCount > 100000000, Math.Round(DownloadCount / 100000000, 2) & " 亿", If(DownloadCount > 100000, Math.Floor(DownloadCount / 10000) & " 万", DownloadCount)) - '其他 - If LogoUrl Is Nothing Then - NewItem.Logo = "pack://application:,,,/images/Icons/NoIcon.png" - Else - NewItem.Logo = LogoUrl - End If Return NewItem End Function + Public Function GetControlLogo() As String + If LogoUrl Is Nothing Then + Return "pack://application:,,,/images/Icons/NoIcon.png" + Else + Return LogoUrl + End If + End Function + Public Function GetControlTitle(HasModLoaderDescription As Boolean) As KeyValuePair(Of String, String) + '检查下列代码时可以参考 #1567 的测试例 + Dim Title As String = RawName + Dim SubtitleList As List(Of String) + If TranslatedName = RawName Then + '没有中文翻译 + '将所有名称分段 + 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 NoSubtitle + '查找其中的缩写、Forge/Fabric 等版本标记 + SubtitleList = 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 AndAlso Name <> "FPS" AndAlso Name <> "HUD" Then + '缩写 + SubtitleList.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 等版本标记 + SubtitleList.Add(Name) + Else + '其他部分 + NormalNameList.Add(Name) + End If + Next + '根据分类后的结果处理 + If NormalNameList.Count = 0 OrElse SubtitleList.Count = 0 Then GoTo NoSubtitle + '同时包含 NormalName 和 Subtitle + Title = NormalNameList.Join(" - ") + Else + '有中文翻译 + '尝试将文本分为三段:Title (EnglishName) - Suffix + '检查时注意 Carpet:它没有中文译名,但有 Suffix + Title = TranslatedName.Split(" (").First.Split(" - ").First + Dim Suffix As String = "" + If TranslatedName.Split(")").Last.Contains(" - ") Then Suffix = TranslatedName.Split(")").Last.Split(" - ").Last + Dim EnglishName As String = TranslatedName + If Suffix <> "" Then EnglishName = EnglishName.Replace(" - " & Suffix, "") + EnglishName = EnglishName.Replace(Title, "").Trim("("c, ")"c, " "c) + '中段的额外信息截取 + SubtitleList = EnglishName.Split({" | ", " - ", "(", ")", "[", "]", "{", "}"}, StringSplitOptions.RemoveEmptyEntries). + Select(Function(s) s.Trim(" /".ToCharArray)).Where(Function(w) Not String.IsNullOrEmpty(w)).ToList + If SubtitleList.Count > 1 AndAlso + Not SubtitleList.Any(Function(s) s.ToLower.Contains("forge") OrElse s.ToLower.Contains("fabric") OrElse s.ToLower.Contains("quilt")) AndAlso '不是标注 XX 版 + Not (SubtitleList.Count = 2 AndAlso SubtitleList.Last.ToUpper = SubtitleList.Last) Then '不是缩写 + SubtitleList = New List(Of String) From {EnglishName} '使用原名 + End If + '添加后缀 + If Suffix <> "" Then SubtitleList.Add(Suffix) + End If + SubtitleList = SubtitleList.Distinct().ToList() + '设置标题与描述 + Dim Subtitle As String = "" + If SubtitleList.Count > 0 Then + For Each Ex In SubtitleList + Dim IsModLoaderDescription As Boolean = + Ex.ToLower.Contains("forge") OrElse Ex.ToLower.Contains("fabric") OrElse Ex.ToLower.Contains("quilt") + '是否显示 ModLoader 信息 + If Not HasModLoaderDescription AndAlso IsModLoaderDescription Then Continue For + '去除 “Forge/Fabric” 这一无意义提示 + If Ex.Length < 16 AndAlso Ex.ToLower.Contains("fabric") AndAlso Ex.ToLower.Contains("forge") Then Continue For + '将 “Forge” 等提示改为 “Forge 版” + If IsModLoaderDescription AndAlso Not Ex.Contains("版") AndAlso + Ex.ToLower.Replace("forge", "").Replace("fabric", "").Replace("quilt", "").Length <= 3 Then + Ex = Ex.Replace("Edition", "").Replace("edition", "").Trim.Capitalize & " 版" + End If + '将 “forge” 等词语的首字母大写 + Ex = Ex.Replace("forge", "Forge").Replace("neo", "Neo").Replace("fabric", "Fabric").Replace("quilt", "Quilt") + Subtitle &= " | " & Ex.Trim + Next + Else +NoSubtitle: + Subtitle = "" + End If + Return New KeyValuePair(Of String, String)(Title, Subtitle) + End Function '辅助函数 @@ -761,6 +811,10 @@ NoExName: ''' 可供展示的所有工程的列表。 ''' Public Results As New List(Of CompProject) + ''' + ''' 当前的错误信息。如果没有则为 Nothing。 + ''' + Public ErrorMessage As String = Nothing End Class @@ -770,7 +824,7 @@ NoExName: ''' ''' 已知工程信息的缓存。 ''' - Private CompProjectCache As New Dictionary(Of String, CompProject) + Public CompProjectCache As New Dictionary(Of String, CompProject) ''' ''' 根据搜索请求获取一系列的工程列表。需要基于加载器运行。 ''' @@ -887,6 +941,7 @@ Retry: '启动 CurseForge 线程 Dim CurseForgeUrl As String = Task.Input.GetCurseForgeAddress() + Dim CurseForgeFailed As Boolean = False If CurseForgeUrl IsNot Nothing Then CurseForgeThread = RunInNewThread( Sub() @@ -910,12 +965,14 @@ Retry: Log(ex, "从 CurseForge 获取工程列表失败") Storage.CurseForgeTotal = Storage.CurseForgeOffset [Error] = ex + CurseForgeFailed = True End Try End Sub, "CurseForge Project Request") End If '启动 Modrinth 线程 Dim ModrinthUrl As String = Task.Input.GetModrinthAddress() + Dim ModrinthFailed As Boolean = False If ModrinthUrl IsNot Nothing Then ModrinthThread = RunInNewThread( Sub() @@ -941,6 +998,7 @@ Retry: Log(ex, "从 Modrinth 获取工程列表失败") Storage.ModrinthTotal = Storage.ModrinthOffset [Error] = ex + ModrinthFailed = True End Try End Sub, "Modrinth Project Request") End If @@ -951,7 +1009,8 @@ Retry: If ModrinthThread IsNot Nothing Then ModrinthThread.Join() If Task.IsAborted Then Exit Sub - '确认存在结果 + '确保存在结果 + Storage.ErrorMessage = Nothing If RawResults.Count = 0 Then If [Error] IsNot Nothing Then Throw [Error] @@ -966,6 +1025,13 @@ Retry: Throw New Exception("没有符合条件的结果") End If End If + ElseIf [Error] IsNot Nothing Then + '有结果但是有错误 + If CurseForgeFailed Then + Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,结果可能不全。{vbCrLf}请尝试使用 VPN 或加速器以改善网络。" + Else + Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,结果可能不全。{vbCrLf}请尝试使用 VPN 或加速器以改善网络。" + End If End If Finally @@ -1160,7 +1226,7 @@ Retry: FileName = Data("fileName") 'DownloadAddress Dim Url = Data("downloadUrl").ToString - If Url = "" Then Url = "https://media.forgecdn.net/files/" & Id.ToString.Substring(0, 4) & "/" & Id.ToString.Substring(4).TrimStart("0") & "/" & FileName + If Url = "" Then Url = $"https://media.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}" Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码 DownloadAddress = {Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"), Url.Replace("-service.overwolf.wtf", ".forgecdn.net"), @@ -1282,7 +1348,6 @@ Retry: ToolTipService.SetHorizontalOffset(BtnSave, 2) AddHandler BtnSave.Click, OnSaveClick NewItem.Buttons = {BtnSave} - NewItem.PaddingRight = 35 End If '结束 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb index 6a5d35c..bd87074 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb @@ -641,7 +641,7 @@ Done: 'If Not Stack.Contains(".") Then Continue For For Each IgnoreStack In { "java", "sun", "javax", "jdk", "oolloo", - "org.lwjgl", "com.sun", "net.minecraftforge", "com.mojang", "net.minecraft", "cpw.mods", "com.google", "org.apache", "org.spongepowered", "net.fabricmc", "com.mumfrey", + "org.lwjgl", "com.sun", "net.minecraftforge", "paulscode.sound", "com.mojang", "net.minecraft", "cpw.mods", "com.google", "org.apache", "org.spongepowered", "net.fabricmc", "com.mumfrey", "com.electronwill.nightconfig", "it.unimi.dsi", "MojangTricksIntelDriversForPerformance_javaw"} If Stack.StartsWith(IgnoreStack) Then GoTo NextStack @@ -666,7 +666,7 @@ NextStack: If {"com", "org", "net", "asm", "fml", "mod", "jar", "sun", "lib", "map", "gui", "dev", "nio", "api", "dsi", "top", "core", "init", "mods", "main", "file", "game", "load", "read", "done", "util", "tile", "item", "base", "oshi", "forge", "setup", "block", "model", "mixin", "event", "unimi", "netty", - "common", "server", "config", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe", + "gitlab", "common", "server", "config", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe", "preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules", "fastutil", "optifine", "internal", "platform", "override", "listeners", "scheduler", "minecraft", "transformers", "universal", "multipart", "minecraftforge", "blockentity" diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb index c321242..bccfc08 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb @@ -455,7 +455,7 @@ ''' Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain) Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult)) - Dim Result As String = NetGetCodeByRequestRetry("http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html") + Dim Result As String = NetGetCodeByRequestRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html") If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") '获取所有版本信息 Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)") @@ -593,7 +593,7 @@ Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry))) Dim Result As String Try - Result = NetGetCodeByDownload("http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input & ".html") + Result = NetGetCodeByDownload("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input & ".html") Catch ex As Exception If GetExceptionSummary(ex).Contains("(404)") Then Throw New Exception("没有可用版本") @@ -799,7 +799,7 @@ ''' Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain) Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("http://dl.liteloader.com/versions/versions.json", IsJson:=True) + Dim Result As JObject = NetGetCodeByRequestRetry("https://dl.liteloader.com/versions/versions.json", IsJson:=True) Try Dim Json As JObject = Result("versions") Dim Versions As New List(Of DlLiteLoaderListEntry) diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb index 8ddecd1..5789b60 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb @@ -135,6 +135,8 @@ If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!") Catch ex As ApplicationException Throw ex + Catch ex As ThreadInterruptedException + Throw ex Catch ex As Exception Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出")) Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex) @@ -605,6 +607,7 @@ NoUserJava: SyncLock ListLock JavaCheckList.Add(Entry) End SyncLock + Catch ex As ThreadInterruptedException Catch ex As Exception If Entry.IsUserImport Then Log(ex, "位于 " & Entry.PathFolder & " 的 Java 存在异常,将被自动移除", LogLevel.Hint) diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb index c3f8c66..03065a9 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb @@ -881,7 +881,7 @@ SystemBrowser: End If Dim Result As String Try - Result = NetRequestMulty("https://login.live.com/oauth20_token.srf", "POST", Request, "application/x-www-form-urlencoded", 1) + Result = NetRequestMulty("https://login.live.com/oauth20_token.srf", "POST", Request, "application/x-www-form-urlencoded", 2) Catch ex As Exception If ex.Message.Contains("must sign in again") OrElse ex.Message.Contains("invalid_grant") Then '#269 Return {"Relogin", ""} @@ -1000,8 +1000,8 @@ SystemBrowser: ElseIf Message.Contains("(404)") Then Log(ex, "微软登录第 6 步汇报 404") RunInNewThread(Sub() - Select Case MyMsgBox("你可能没有在 Minecraft 官网创建档案,Xbox Game Pass 已到期,或者还没有购买 Minecraft。" & vbCrLf & - "如果你确认拥有 Minecraft,请在官网上创建档案后再试。", "登录失败", "创建档案", "购买 Minecraft", "取消") + Select Case MyMsgBox("你可能没有创建 Minecraft 玩家档案、Xbox Game Pass 已到期,或者还没有购买 Minecraft。" & vbCrLf & + "请先使用官方启动器登录一次,之后即可使用 PCL 登录。", "登录失败", "创建档案", "购买 Minecraft", "取消") Case 1 OpenWebsite("https://www.minecraft.net/zh-hans/msaprofile/mygames/editprofile") Case 2 @@ -1210,6 +1210,7 @@ SystemBrowser: Loop '检查下载结果 + If JavaSearchLoader.State <> LoadState.Loading Then JavaSearchLoader.State = LoadState.Waiting '2872# McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent) If Task.IsAborted Then Exit Sub If McLaunchJavaSelected IsNot Nothing Then @@ -1857,7 +1858,7 @@ NextVersion: End If Case Else '快照版是 99 PackFormat = 17 - 'https://minecraft.fandom.com/zh/wiki/数据包#数据包版本 + 'https://zh.minecraft.wiki/w/数据包#数据包版本 End Select McLaunchLog("正在构建自定义皮肤资源包,格式为:" & PackFormat) '准备文件 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb index f3b9d73..84288a8 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb @@ -1958,9 +1958,7 @@ NextVersion: Dim Urls As New List(Of String) If Token.Url IsNot Nothing Then '获取 Url 的真实地址 - Dim Url As String = Token.Url - Url = Url.Replace("https://files.minecraftforge.net", "http://files.minecraftforge.net") '这个鬼东西不让 https - Urls.Add(Url) + Urls.Add(Token.Url) If Token.Url.Contains("launcher.mojang.com/v1/objects") Then Urls = DlSourceLauncherOrMetaGet(Token.Url).ToList() 'Mappings If Token.Url.Contains("maven") Then Urls.Insert(0, Token.Url.Replace(Mid(Token.Url, 1, Token.Url.IndexOf("maven")), "https://download.mcbbs.net/").Replace("maven.fabricmc.net", "maven").Replace("maven.minecraftforge.net", "maven")) End If @@ -1974,8 +1972,8 @@ NextVersion: Dim OptiFineBase As String = Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries\optifine\OptiFine\", "").Split("_")(0) & "/" & GetFileNameFromPath(Token.LocalPath).Replace("-", "_") OptiFineBase = "/maven/com/optifine/" & OptiFineBase If OptiFineBase.Contains("_pre") Then OptiFineBase = OptiFineBase.Replace("com/optifine/", "com/optifine/preview_") - Urls.Add("http://download.mcbbs.net" & OptiFineBase) - Urls.Add("http://bmclapi2.bangbang93.com" & OptiFineBase) + Urls.Add("https://download.mcbbs.net" & OptiFineBase) + Urls.Add("https://bmclapi2.bangbang93.com" & OptiFineBase) ElseIf Urls.Count <= 2 Then '普通文件 Urls.AddRange(DlSourceLibraryGet("https://libraries.minecraft.net" & Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries", "").Replace("\", "/"))) @@ -2188,7 +2186,7 @@ NextVersion: '检查文件是否存在 Dim File As New FileInfo(Token.LocalPath) If File.Exists AndAlso (Token.Size = 0 OrElse Token.Size = File.Length) AndAlso - (Not CheckHash OrElse Token.Hash Is Nothing OrElse Token.Hash = GetAuthSHA1(Token.LocalPath)) Then Continue For + (Not CheckHash OrElse Token.Hash Is Nothing OrElse Token.Hash = GetFileSHA1(Token.LocalPath)) Then Continue For '文件不存在,添加下载 Result.Add(New NetFile(DlSourceResourceGet("https://resources.download.minecraft.net/" & Left(Token.Hash, 2) & "/" & Token.Hash), Token.LocalPath, New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.Hash))) Next diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb index 4088f1b..8789d74 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb @@ -274,31 +274,25 @@ Public Module ModMod If IsLoaded AndAlso Not ForceReload Then Exit Sub '初始化 Init() - '阶段 1:基础可用性检查 - Dim Jar As ZipArchive + Dim Jar As ZipArchive = Nothing Try - '打开 Jar 文件 + '阶段 1:基础可用性检查、打开 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("文件内容为空") + '阶段 2:信息获取 + LoadWithoutClass(Jar) + '阶段 3: Class 信息获取 + If Not IsInfoWithoutClassAvailable Then LoadWithClass(Jar) 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 + Finally If Jar IsNot Nothing Then Jar.Dispose() - Catch End Try '完成标记 IsLoaded = True @@ -695,6 +689,85 @@ VersionFindFail: IsInfoWithClassAvailable = _ModId IsNot Nothing AndAlso _Version IsNot Nothing End Sub +#End Region + +#Region "网络信息" + + ''' + ''' 该 Mod 关联的网络项目。 + ''' + Public Property Comp As CompProject + Get + Return _Comp + End Get + Set(value As CompProject) + _Comp = value + RaiseEvent OnGetCompProject(Me) + End Set + End Property + Private _Comp As CompProject + Public IsCompFromModrinth = False + Public Event OnGetCompProject(sender As McMod) + + ''' + ''' 获取用于 CurseForge 信息获取的 Hash 值(MurmurHash2)。 + ''' + Public ReadOnly Property CurseForgeHash As UInteger + Get + If _CurseForgeHash Is Nothing Then + '读取文件 + Dim data As New List(Of Byte) + For Each b As Byte In File.ReadAllBytes(Path) + If b = 9 OrElse b = 10 OrElse b = 13 OrElse b = 32 Then Continue For + data.Add(b) + Next + '计算 MurmurHash2 + Dim length As Integer = data.Count + Dim h As UInteger = 1 Xor length '1 是种子 + Dim i As Integer + For i = 0 To length - 4 Step 4 + Dim k As UInteger = data(i) Or CUInt(data(i + 1)) << 8 Or CUInt(data(i + 2)) << 16 Or CUInt(data(i + 3)) << 24 + k = (k * &H5BD1E995L) And &HFFFFFFFFL + k = k Xor (k >> 24) + k = (k * &H5BD1E995L) And &HFFFFFFFFL + h = (h * &H5BD1E995L) And &HFFFFFFFFL + h = h Xor k + Next + Select Case length - i + Case 3 + h = h Xor (data(i) Or CUInt(data(i + 1)) << 8) + h = h Xor (CUInt(data(i + 2)) << 16) + h = (h * &H5BD1E995L) And &HFFFFFFFFL + Case 2 + h = h Xor (data(i) Or CUInt(data(i + 1)) << 8) + h = (h * &H5BD1E995L) And &HFFFFFFFFL + Case 1 + h = h Xor data(i) + h = (h * &H5BD1E995L) And &HFFFFFFFFL + End Select + h = h Xor (h >> 13) + h = (h * &H5BD1E995L) And &HFFFFFFFFL + h = h Xor (h >> 15) + _CurseForgeHash = h + End If + Return _CurseForgeHash + End Get + End Property + Private _CurseForgeHash As UInteger? + + ''' + ''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。 + ''' + Public ReadOnly Property ModrinthHash As String + Get + If _ModrinthHash Is Nothing Then + _ModrinthHash = GetFileSHA1(Path) + End If + Return _ModrinthHash + End Get + End Property + Private _ModrinthHash As String + #End Region ''' @@ -741,18 +814,49 @@ VersionFindFail: End If '确定是否显示进度 - Loader.Progress = 0.03 - If ModFileList.Count > 100 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True) + Loader.Progress = 0.05 + If ModFileList.Count > 50 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True) - '加载为 Mod 列表 + '获取本地文件缓存 + Dim CachePath As String = PathTemp & "Cache\LocalMod.json" + Dim Cache As New JObject + Try + Dim CacheContent As String = ReadFile(CachePath) + If CacheContent <> "" Then + Cache = GetJson(CacheContent) + If Not Cache.ContainsKey("version") OrElse Cache("version").ToObject(Of Integer) <> LocalModCacheVersion Then + Log($"[Mod] 本地 Mod 信息缓存版本已过期,将弃用这些缓存信息") + Cache = New JObject + End If + End If + Catch ex As Exception + Log(ex, "读取本地 Mod 信息缓存失败,已重置") + End Try + Cache("version") = LocalModCacheVersion + + '加载 Mod 列表 Dim ModList As New List(Of McMod) + Dim ModUpdateList 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 + Loader.Progress += 0.95 / ModFileList.Count + If Loader.IsAborted Then Exit Sub + '加载 McMod 对象 + Dim ModEntry As New McMod(ModFile.FullName) + ModEntry.Load() + ModList.Add(ModEntry) + '读取 Comp 缓存 + If ModEntry.State = McMod.McModState.Unavaliable Then Continue For + Dim Hash = ModEntry.ModrinthHash + If Cache.ContainsKey(Hash) Then + ModEntry.Comp = New CompProject(Cache(Hash)) + ModEntry.IsCompFromModrinth = Not ModEntry.Comp.FromCurseForge + '如果缓存中的信息在 6 小时以内更新过,则无需重新获取 + If Date.Now - Cache(Hash)("CacheTime").ToObject(Of Date) < New TimeSpan(6, 0, 0) Then Continue For + End If + ModUpdateList.Add(ModEntry) Next - Loader.Progress = 0.93 + Loader.Progress = 0.99 + Log($"[Mod] 共有 {ModList.Count} 个 Mod,其中 {ModUpdateList.Where(Function(m) m.Comp Is Nothing).Count} 个需要联网获取信息,{ModUpdateList.Where(Function(m) m.Comp IsNot Nothing).Count} 个需要更新信息") '排序 ModList = Sort(ModList, Function(Left As McMod, Right As McMod) As Boolean @@ -762,18 +866,158 @@ VersionFindFail: Return Not Right.FileName.CompareTo(Left.FileName) End If End Function) - Loader.Progress = 0.98 '回设 If Loader.IsAborted Then Exit Sub Loader.Output = ModList + '开始联网加载 + If ModUpdateList.Any() Then + McModDetailLoader.Start(New KeyValuePair(Of List(Of McMod), JObject)(ModUpdateList, Cache), IsForceRestart:=True) + End If + + Loader.Progress = 1 Catch ex As Exception Log(ex, "Mod 列表加载失败", LogLevel.Debug) Throw End Try End Sub + '联网加载 Mod 详情 + Private Const LocalModCacheVersion As Integer = 1 + Public McModDetailLoader As New LoaderTask(Of KeyValuePair(Of List(Of McMod), JObject), Integer)("Mod List Detail Loader", AddressOf McModDetailLoad) + Private Sub McModDetailLoad(Loader As LoaderTask(Of KeyValuePair(Of List(Of McMod), JObject), Integer)) + Dim Mods As List(Of McMod) = Loader.Input.Key + Dim Cache As JObject = Loader.Input.Value + '获取作为检查目标的加载器和版本 + Dim TargetMcVersion As McVersionInfo = PageVersionLeft.Version.Version + Dim ModLoaders As New List(Of String) + If TargetMcVersion.HasForge Then ModLoaders.Add("forge") + If TargetMcVersion.HasFabric Then ModLoaders.Add("fabric") + If TargetMcVersion.HasLiteLoader Then ModLoaders.Add("liteloader") + If Not ModLoaders.Any() Then ModLoaders.AddRange({"forge", "fabric", "liteloader"}) + Dim McVersions As New List(Of String) From {TargetMcVersion.McName} + If TargetMcVersion.McCodeMain > 0 AndAlso TargetMcVersion.McCodeMain < 99 Then + McVersions.Add($"1.{TargetMcVersion.McCodeMain}") + For i = 1 To TargetMcVersion.McCodeSub + McVersions.Add($"1.{TargetMcVersion.McCodeMain}.{i}") + Next + End If + McVersions = McVersions.Distinct().ToList() + '开始网络获取 + Log($"[Mod] 目标加载器:{ModLoaders.Join("/")},版本:{McVersions.Join("/")}") + Dim CompletedThread As Integer = 0 + Dim MainThread As Thread = Thread.CurrentThread + '从 Modrinth 获取信息 + RunInNewThread( + Sub() + Try + '步骤 1:获取 Hash 与对应的工程 ID + Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList() + Dim ModrinthUpdate = CType(GetJson(NetRequestRetry("https://api.modrinth.com/v2/version_files/update", "POST", + $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1"", + ""loaders"": [""{ModLoaders.Join(""",""")}""],""game_versions"": [""{McVersions.Join(""",""")}""]}}", "application/json")), JObject) + Log($"[Mod] 从 Modrinth 获取到 {ModrinthUpdate.Count} 个本地 Mod 的对应信息") + '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系 + If ModrinthUpdate.Count = 0 Then Exit Sub + Dim ModrinthMapping As New Dictionary(Of String, McMod) + For Each ModEntity In Mods + If Not ModrinthUpdate.ContainsKey(ModEntity.ModrinthHash) Then Continue For + Dim ProjectId = ModrinthUpdate(ModEntity.ModrinthHash)("project_id").ToString + If CompProjectCache.ContainsKey(ProjectId) Then + ModEntity.Comp = CompProjectCache(ProjectId) + Else + ModrinthMapping.Add(ProjectId, ModEntity) + End If + Next + If Loader.IsAbortedWithThread(MainThread) Then Exit Sub + Log($"[Mod] 读取缓存后还需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息") + '步骤 3:获取工程信息 + If Not ModrinthMapping.Any() Then Exit Sub + Dim ModrinthProject = CType(GetJson(NetRequestRetry( + $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]", + "GET", "", "application/json")), JArray) + For Each ProjectJson In ModrinthProject + Dim Project As New CompProject(ProjectJson) + Dim Entry = ModrinthMapping(Project.Id) + If Entry.Comp IsNot Nothing AndAlso Not Entry.IsCompFromModrinth Then + Project.LogoUrl = Entry.Comp.LogoUrl 'Modrinth 部分 Logo 加载不出来 + End If + Entry.IsCompFromModrinth = True + Entry.Comp = Project + Next + Log($"[Mod] 从 Modrinth 获取本地 Mod 信息结束") + Catch ex As Exception + Log(ex, "从 Modrinth 获取本地 Mod 信息失败") + Finally + CompletedThread += 1 + End Try + End Sub, "Mod List Detail Loader Modrinth") + '从 CurseForge 获取信息 + RunInNewThread( + Sub() + Try + '步骤 1:获取 Hash 与对应的工程 ID + Dim CurseForgeHashes As New List(Of UInteger) + For Each ModEntity In Mods + CurseForgeHashes.Add(ModEntity.CurseForgeHash) + If Loader.IsAbortedWithThread(MainThread) Then Exit Sub + Next + Dim CurseForgeRaw = CType(CType(GetJson(NetRequestRetry("https://api.curseforge.com/v1/fingerprints/432/", "POST", + $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer) + Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息") + '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系 + If CurseForgeRaw.Count = 0 Then Exit Sub + Dim CurseForgeMapping As New Dictionary(Of Integer, McMod) + For Each Project In CurseForgeRaw + Dim ProjectId = Project("id").ToString + Dim Hash As UInteger = Project("file")("fileFingerprint") + Dim ModEntity = Mods.Find(Function(m) m.CurseForgeHash = Hash) + If CompProjectCache.ContainsKey(ProjectId) Then + ModEntity.Comp = CompProjectCache(ProjectId) + Else + CurseForgeMapping.Add(ProjectId, ModEntity) + End If + Next + If Loader.IsAbortedWithThread(MainThread) Then Exit Sub + Log($"[Mod] 读取缓存后还需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息") + '步骤 3:获取工程信息 + If Not CurseForgeMapping.Any() Then Exit Sub + Dim CurseForgeProject = CType(GetJson(NetRequestRetry("https://api.curseforge.com/v1/mods", "POST", + $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data") + For Each ProjectJson In CurseForgeProject + Dim Project As New CompProject(ProjectJson) + Dim Entry = CurseForgeMapping(Project.Id) '倒查防止 CurseForge 返回的内容有漏 + If Entry.Comp IsNot Nothing AndAlso Entry.IsCompFromModrinth Then + Entry.Comp.LogoUrl = Project.LogoUrl 'Modrinth 部分 Logo 加载不出来 + Entry.Comp = Entry.Comp '再次触发修改事件 + Continue For + End If + Entry.IsCompFromModrinth = False + Entry.Comp = Project + Next + Log($"[Mod] 从 CurseForge 获取本地 Mod 信息结束") + Catch ex As Exception + Log(ex, "从 CurseForge 获取本地 Mod 信息失败") + Finally + CompletedThread += 1 + End Try + End Sub, "Mod List Detail Loader CurseForge") + '等待线程结束 + Do Until CompletedThread = 2 + If Loader.IsAborted Then Exit Sub + Thread.Sleep(10) + Loop + '保存缓存 + Mods = Mods.Where(Function(m) m.Comp IsNot Nothing).ToList() + Log($"[Mod] 联网获取本地 Mod 信息完成,为 {Mods.Count} 个 Mod 更新缓存") + If Not Mods.Any() Then Exit Sub + For Each ModEntity In Mods + Cache(ModEntity.ModrinthHash) = ModEntity.Comp.ToJson() + Next + WriteFile(PathTemp & "Cache\LocalMod.json", Cache.ToString(If(ModeDebug, Newtonsoft.Json.Formatting.Indented, Newtonsoft.Json.Formatting.None))) + End Sub + #If DEBUG Then ''' ''' 检查 Mod 列表中存在的错误,返回错误信息的集合。 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml b/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml index cde9262..8fd7bae 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml +++ b/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml @@ -29,15 +29,15 @@ - + - + - textBlock.ActualWidth + End Function + + 'Tag + Public WriteOnly Property Tags As List(Of String) + Set(value As List(Of String)) + PanTags.Children.Clear() + PanTags.Visibility = If(value.Any(), Visibility.Visible, Visibility.Collapsed) + For Each TagText In value + Dim NewTag = GetObjectFromXML( + " + + ") + PanTags.Children.Add(NewTag) + Next + End Set + End Property + #End Region #Region "点击" @@ -104,13 +171,28 @@ RetryStart: Log("[Comp] 记录当前已展开的卡片:" & String.Join("、", Titles)) FrmMain.PageCurrent.Additional(1) = Titles End If - FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail, .Additional = {sender.Tag, New List(Of String)}}) + '打开详情页 + Dim TargetVersion As String, TargetLoader As CompModLoaderType + Select Case CType(sender.Tag, CompProject).Type + Case CompType.Mod + TargetVersion = If(PageDownloadMod.Loader.Input.GameVersion, "") + Case CompType.ModPack + TargetVersion = If(PageDownloadPack.Loader.Input.GameVersion, "") + Case Else 'CompType.ResourcePack + 'FUTURE: Res + TargetVersion = "" 'If(PageDownloadResource.Loader.Input.GameVersion, "") + End Select + If CType(sender.Tag, CompProject).Type = CompType.Mod AndAlso PageDownloadMod.Loader.Input.ModLoader <> CompModLoaderType.Any Then + TargetLoader = PageDownloadMod.Loader.Input.ModLoader + End If + FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail, + .Additional = {sender.Tag, New List(Of String), TargetVersion, TargetLoader}}) End Sub '鼠标点击判定 Private IsMouseDown As Boolean = False Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown - If IsMouseOver Then IsMouseDown = True + If IsMouseOver AndAlso CanInteraction Then IsMouseDown = True End Sub Private Sub Button_MouseLeave(sender As Object, e As Object) Handles Me.MouseLeave, Me.PreviewMouseLeftButtonUp IsMouseDown = False @@ -151,9 +233,12 @@ RetryStart: #End Region Private StateLast As String - Public HasAnimation As Boolean = True + ''' + ''' 是否允许交互。目前仅用于 PageDownloadCompDetail 的顶部栏展示:若关闭碰撞检测,则无法展开 Tooltip。 + ''' + Public CanInteraction As Boolean = True Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp - If Not HasAnimation Then Exit Sub + If Not CanInteraction Then Exit Sub '判断当前颜色 Dim StateNew As String, Time As Integer If IsMouseOver Then @@ -200,20 +285,4 @@ RetryStart: End If End Sub - '指向时扩展描述 - Private Sub LabInfo_MouseEnter(sender As Object, e As MouseEventArgs) Handles LabInfo.MouseEnter - If IsTextTrimmed(LabInfo) Then - ToolTipInfo.Content = LabInfo.Text - ToolTipInfo.Width = LabInfo.ActualWidth + 25 - LabInfo.ToolTip = ToolTipInfo - Else - LabInfo.ToolTip = Nothing - End If - End Sub - Private Function IsTextTrimmed(textBlock As TextBlock) As Boolean - Dim typeface As New Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch) - Dim formattedText As New FormattedText(textBlock.Text, Thread.CurrentThread.CurrentCulture, textBlock.FlowDirection, typeface, textBlock.FontSize, textBlock.Foreground, DPI) - Return formattedText.Width > textBlock.ActualWidth - End Function - End Class diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml new file mode 100644 index 0000000..f7470c2 --- /dev/null +++ b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb new file mode 100644 index 0000000..6eaf88e --- /dev/null +++ b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb @@ -0,0 +1,453 @@ +Imports System.Windows.Forms + +Public Class MyLocalModItem + +#Region "基础属性" + Public Uuid As Integer = GetUuid() + + 'Logo + Private _Logo As String = "" + Public Property Logo As String + Get + Return _Logo + End Get + Set(value As String) + If _Logo = value OrElse value Is Nothing Then Exit Property + _Logo = value + Dim FileAddress = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png" + Try + If _Logo.ToLower.StartsWith("http") Then + '网络图片 + If File.Exists(FileAddress) Then + PathLogo.Source = New MyBitmap(FileAddress) + ElseIf _Logo.ToLower.EndsWith(".webp") Then 'Modrinth 林业 Mod 使用了不支持的 WebP 格式 Logo + Log($"[LocalModItem] 发现不支持的 WebP 格式图标,已更改为默认图标:{_Logo}") + PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png") + Else + PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png") + RunInNewThread(Sub() LogoLoader(FileAddress), "Comp Logo Loader " & Uuid & "#", ThreadPriority.BelowNormal) + End If + Else + '位图 + PathLogo.Source = New MyBitmap(_Logo) + End If + Catch ex As IOException + Log(ex, "加载本地 Mod 图标时读取失败(" & FileAddress & ")") + Catch ex As ArgumentException + '考虑缓存的图片本身可能有误 + Log(ex, "可视化本地 Mod 图标失败(" & FileAddress & ")") + Try + File.Delete(FileAddress) + Log("[LocalModItem] 已清理损坏的本地 Mod 图标:" & FileAddress) + Catch exx As Exception + Log(exx, "清理损坏的本地 Mod 图标缓存失败(" & FileAddress & ")", LogLevel.Hint) + End Try + Catch ex As Exception + Log(ex, "加载本地 Mod 图标失败(" & value & ")") + End Try + End Set + End Property + '后台加载 Logo + Private Sub LogoLoader(Address As String) + Dim Retry As Boolean = False + Dim DownloadEnd As String = GetUuid() +RetryStart: + Try + NetDownload(_Logo, Address & DownloadEnd) + Dim LoadError As Exception = Nothing + RunInUiWait(Sub() + Try + '在地址更换时取消加载 + If Not Address = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png" Then Exit Sub + '在完成正常加载后才保存缓存图片 + PathLogo.Source = New MyBitmap(Address & DownloadEnd) + Catch ex As Exception + Log(ex, "读取本地 Mod 图标失败(" & Address & ")") + File.Delete(Address & DownloadEnd) + LoadError = ex + End Try + End Sub) + If LoadError IsNot Nothing Then Throw LoadError + If File.Exists(Address) Then + File.Delete(Address & DownloadEnd) + Else + FileIO.FileSystem.MoveFile(Address & DownloadEnd, Address) + End If + Catch ex As Exception + If Not Retry Then + Retry = True + GoTo RetryStart + Else + Log(ex, $"下载本地 Mod 图标失败({_Logo})") + RunInUi(Sub() PathLogo.Source = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png")) + End If + End Try + End Sub + + '标题 + Private _Title As String + Public Property Title As String + Get + Return _Title + End Get + Set(value As String) + Dim RawValue = value + Select Case Entry.State + Case McMod.McModState.Disabled + value &= " [禁用]" + Case McMod.McModState.Unavaliable + value &= " [错误]" + End Select + If LabTitle.Text = value Then Exit Property + LabTitle.Text = value + _Title = RawValue + End Set + End Property + + '副标题 + Public Property SubTitle As String + Get + Return If(LabTitleRaw?.Text, "") + End Get + Set(value As String) + If LabTitleRaw.Text = value Then Exit Property + LabTitleRaw.Text = value + LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible) + End Set + End Property + + '描述 + Public Property Description As String + Get + Return LabInfo.Text + End Get + Set(value As String) + If LabInfo.Text = value Then Exit Property + LabInfo.Text = value + End Set + End Property + + 'Tag + Public WriteOnly Property Tags As List(Of String) + Set(value As List(Of String)) + PanTags.Children.Clear() + PanTags.Visibility = If(value.Any(), Visibility.Visible, Visibility.Collapsed) + For Each TagText In value + Dim NewTag = GetObjectFromXML( + " + + ") + PanTags.Children.Add(NewTag) + Next + End Set + End Property + + '相关联的 Mod + Public Property Entry As McMod + Get + Return Tag + End Get + Set(value As McMod) + Tag = value + End Set + End Property + + '前景色绑定 + Public Property Foreground As Brush + Get + Return GetValue(ForegroundProperty) + End Get + Set(value As Brush) + SetValue(ForegroundProperty, value) + End Set + End Property + Public Shared ReadOnly ForegroundProperty As DependencyProperty = DependencyProperty.Register("Foreground", GetType(Brush), GetType(MyLocalModItem), New PropertyMetadata(CType(Color1, SolidColorBrush))) + +#End Region + +#Region "点击与勾选" + + '触发点击事件 + Public Event Click(sender As Object, e As MouseButtonEventArgs) + Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp + If IsMouseDown Then + RaiseEvent Click(sender, e) + If e.Handled Then Exit Sub + Log("[Control] 按下本地 Mod 列表项:" & LabTitle.Text) + End If + End Sub + + '鼠标点击判定 + Private IsMouseDown As Boolean = False + Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown + If Not IsMouseDirectlyOver Then Exit Sub + IsMouseDown = True + If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = False + End Sub + Private Sub Button_MouseLeave(sender As Object, e As Object) Handles Me.MouseLeave, Me.PreviewMouseLeftButtonUp + IsMouseDown = False + If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True + End Sub + + '勾选状态 + Public Event Check(sender As Object, e As RouteEventArgs) + Public Event Changed(sender As Object, e As RouteEventArgs) + Private _Checked As Boolean = False + Public Property Checked As Boolean + Get + Return _Checked + End Get + Set(value As Boolean) + Try + '触发属性值修改 + Dim ChangedEventArgs As New RouteEventArgs(False) + Dim RawValue = _Checked + If value = _Checked Then Exit Property + _Checked = value + If IsInitialized Then + RaiseEvent Changed(Me, ChangedEventArgs) + If ChangedEventArgs.Handled Then + _Checked = RawValue + Exit Property + End If + End If + If value Then + Dim CheckEventArgs As New RouteEventArgs(False) + RaiseEvent Check(Me, CheckEventArgs) + If CheckEventArgs.Handled Then Exit Property + End If + '更改动画 + If IsVisibleInForm() Then + Dim Anim As New List(Of AniData) + If Checked Then + '由无变有 + Dim Delta = 32 - RectCheck.ActualHeight + Anim.Add(AaHeight(RectCheck, Delta * 0.4, 200,, New AniEaseOutFluent(AniEasePower.Weak))) + Anim.Add(AaHeight(RectCheck, Delta * 0.6, 300,, New AniEaseOutBack(AniEasePower.Weak))) + Anim.Add(AaOpacity(RectCheck, 1 - RectCheck.Opacity, 30)) + RectCheck.VerticalAlignment = VerticalAlignment.Center + RectCheck.Margin = New Thickness(-3, 0, 0, 0) + Anim.Add(AaColor(Me, ForegroundProperty, "ColorBrush2", 200)) + Else + '由有变无 + Anim.Add(AaHeight(RectCheck, -RectCheck.ActualHeight, 120,, New AniEaseInFluent(AniEasePower.Weak))) + Anim.Add(AaOpacity(RectCheck, -RectCheck.Opacity, 70, 40)) + RectCheck.VerticalAlignment = VerticalAlignment.Center + Anim.Add(AaColor(Me, ForegroundProperty, "ColorBrush1", 120)) + End If + AniStart(Anim, "MyLocalModItem Checked " & Uuid) + Else + '不在窗口上时直接设置 + RectCheck.VerticalAlignment = VerticalAlignment.Center + RectCheck.Margin = New Thickness(-3, 0, 0, 0) + If Checked Then + RectCheck.Height = 32 + RectCheck.Opacity = 1 + Foreground = CType(FindResource("ColorBrush2"), SolidColorBrush) + Else + RectCheck.Height = 0 + RectCheck.Opacity = 0 + Foreground = CType(FindResource("ColorBrush1"), SolidColorBrush) + End If + AniStop("MyLocalModItem Checked " & Uuid) + End If + + Catch ex As Exception + Log(ex, "设置 Checked 失败") + End Try + End Set + End Property + Private Sub ColorInit(sender As Object, e As RoutedEventArgs) Handles Me.Loaded + SetResourceReference(ForegroundProperty, "ColorBrush1") + End Sub + + +#End Region + +#Region "后加载内容" + + '右下角状态指示图标 + Private ImgState As Image + + '指向背景 + Private _RectBack As Border = Nothing + Public ReadOnly Property RectBack As Border + Get + If _RectBack Is Nothing Then + Dim Rect As New Border With { + .Name = "RectBack", + .CornerRadius = New CornerRadius(3), + .RenderTransform = New ScaleTransform(0.8, 0.8), + .RenderTransformOrigin = New Point(0.5, 0.5), + .BorderThickness = New Thickness(GetWPFSize(1)), + .SnapsToDevicePixels = True, + .IsHitTestVisible = False, + .Opacity = 0 + } + Rect.SetResourceReference(Border.BackgroundProperty, "ColorBrush7") + Rect.SetResourceReference(Border.BorderBrushProperty, "ColorBrush6") + SetColumnSpan(Rect, 999) + SetRowSpan(Rect, 999) + Children.Insert(0, Rect) + _RectBack = Rect + ' + End If + Return _RectBack + End Get + End Property + + '按钮 + Public ButtonHandler As Action(Of MyLocalModItem, EventArgs) + Public ButtonStack As FrameworkElement + Private _Buttons As IEnumerable(Of MyIconButton) + Public Property Buttons As IEnumerable(Of MyIconButton) + Get + Return _Buttons + End Get + Set(value As IEnumerable(Of MyIconButton)) + _Buttons = value + '移除原 Stack + If ButtonStack IsNot Nothing Then + Children.Remove(ButtonStack) + ButtonStack = Nothing + End If + If value.Count = 0 Then Exit Property + '添加新 Stack + ButtonStack = New StackPanel With {.Opacity = 0, .Margin = New Thickness(0, 0, 5, 0), .SnapsToDevicePixels = False, .Orientation = Orientation.Horizontal, + .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Center, .UseLayoutRounding = False} + SetColumnSpan(ButtonStack, 10) : SetRowSpan(ButtonStack, 10) + '构造按钮 + For Each Btn As MyIconButton In value + If Btn.Height.Equals(Double.NaN) Then Btn.Height = 25 + If Btn.Width.Equals(Double.NaN) Then Btn.Width = 25 + CType(ButtonStack, StackPanel).Children.Add(Btn) + Next + Children.Add(ButtonStack) + End Set + End Property + + '勾选条 + Private _RectCheck As Border + Public ReadOnly Property RectCheck As Border + Get + If _RectCheck Is Nothing Then + _RectCheck = New Border With {.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2), + .VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center), + .HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False, + .Margin = If(Checked, New Thickness(-3, 6, 0, 6), New Thickness(-3, 0, 0, 0))} + _RectCheck.SetResourceReference(Border.BackgroundProperty, "ColorBrush3") + SetRowSpan(_RectCheck, 10) + Children.Add(_RectCheck) + End If + Return _RectCheck + End Get + End Property + +#End Region + + Public Sub Refresh() + RunInUi( + Sub() + '标题 + If Entry.Comp Is Nothing Then + Title = Entry.Name + SubTitle = If(Entry.Version Is Nothing, "", " | " & Entry.Version) + Else + Dim Titles = Entry.Comp.GetControlTitle(False) + Title = Titles.Key + SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", " | " & Entry.Version) + End If + '描述 + Dim NewDescription As String + Select Case Entry.State + Case McMod.McModState.Fine + NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path) + Case McMod.McModState.Disabled + NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path.Substring(0, Entry.Path.Count - ".disabled".Count)) + Case Else 'McMod.McModState.Unavaliable + NewDescription = GetFileNameFromPath(Entry.Path) + End Select + If Entry.Comp IsNot Nothing Then + NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "") + ElseIf Entry.Description IsNot Nothing Then + NewDescription += ": " & Entry.Description.Replace(vbCr, "").Replace(vbLf, "") + ElseIf Not Entry.IsFileAvailable Then + NewDescription += ": " & "存在错误,无法获取信息" + End If + Description = NewDescription + '主 Logo + Logo = If(Entry.Comp Is Nothing, "pack://application:,,,/images/Icons/NoIcon.png", Entry.Comp.GetControlLogo()) + '图标右下角的 Logo + If Entry.State = McMod.McModState.Fine Then + If ImgState IsNot Nothing Then + Children.Remove(ImgState) + ImgState = Nothing + End If + Else + If ImgState Is Nothing Then + ImgState = New Image With { + .Width = 17, .Height = 17, .Margin = New Thickness(0, 0, -4, -3), .IsHitTestVisible = False, + .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Bottom + } + RenderOptions.SetBitmapScalingMode(ImgState, BitmapScalingMode.HighQuality) + SetColumn(ImgState, 1) : SetRow(ImgState, 1) : SetRowSpan(ImgState, 2) + Children.Add(ImgState) + ' + End If + ImgState.Source = New MyBitmap($"pack://application:,,,/images/Icons/{Entry.State}.png") + End If + '标签 + If Entry.Comp IsNot Nothing Then Tags = Entry.Comp.Tags + End Sub) + End Sub + + Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp, Me.Changed + '按钮虚拟化检测 + If ButtonHandler IsNot Nothing Then + ButtonHandler(sender, e) + ButtonHandler = Nothing + End If + '触发颜色动画 + Dim Time As Integer = If(IsMouseOver, 120, 180) + Dim Ani As New List(Of AniData) + 'ButtonStack + If ButtonStack IsNot Nothing Then + If IsMouseOver Then + Ani.Add(AaOpacity(ButtonStack, 1 - ButtonStack.Opacity, Time * 0.7, Time * 0.3)) + Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)), + 5 + Buttons.Count * 25 - ColumnPaddingRight.Width.Value, Time * 0.3, Time * 0.7)) + Else + Ani.Add(AaOpacity(ButtonStack, -ButtonStack.Opacity, Time * 0.4)) + Ani.Add(AaDouble(Sub(i) ColumnPaddingRight.Width = New GridLength(Math.Max(0, ColumnPaddingRight.Width.Value + i)), + 4 - ColumnPaddingRight.Width.Value, Time * 0.4)) + End If + End If + 'RectBack + If IsMouseOver OrElse Checked Then + Ani.AddRange({ + AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrushBg1"), Time), + AaOpacity(RectBack, 1 - RectBack.Opacity, Time,, New AniEaseOutFluent) + }) + If IsMouseDown Then + Ani.Add(AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent)) + Else + Ani.Add(AaScaleTransform(RectBack, 1 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent)) + End If + Else + Ani.AddRange({ + AaOpacity(RectBack, -RectBack.Opacity, Time), + AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent), + AaScaleTransform(RectBack, -0.196, 1,,, True) + }) + End If + AniStart(Ani, "LocalModItem Color " & Uuid) + End Sub + +End Class diff --git a/Plain Craft Launcher 2/Modules/ModMusic.vb b/Plain Craft Launcher 2/Modules/ModMusic.vb index 1f8f7d7..ed1a234 100644 --- a/Plain Craft Launcher 2/Modules/ModMusic.vb +++ b/Plain Craft Launcher 2/Modules/ModMusic.vb @@ -46,7 +46,7 @@ ''' Private Function DequeueNextMusicAddress() As String '初始化,确保存在音乐 - If MusicAllList Is Nothing OrElse MusicAllList.Count = 0 Then MusicListInit(False) + If MusicAllList Is Nothing OrElse MusicAllList.Count = 0 OrElse MusicWaitingList.Count = 0 Then MusicListInit(False) '出列下一个音乐,如果出列结束则生成新列表 DequeueNextMusicAddress = MusicWaitingList(0) MusicWaitingList.RemoveAt(0) @@ -267,6 +267,7 @@ '当前音乐已播放结束,继续下一曲 If CurrentWave.PlaybackState = NAudio.Wave.PlaybackState.Stopped AndAlso MusicAllList.Count > 0 Then MusicStartPlay(DequeueNextMusicAddress) Catch ex As Exception + Log("[Music] 播放音乐失败的文件完整路径:" & MusicCurrent) If ex.Message.Contains("Got a frame at sample rate") OrElse ex.Message.Contains("does not support changes to") Then Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 不支持播放音频属性在中途发生变化的音乐", HintType.Critical) ElseIf Not (MusicCurrent.ToLower.EndsWith(".wav") OrElse MusicCurrent.ToLower.EndsWith(".mp3") OrElse MusicCurrent.ToLower.EndsWith(".flac")) Then @@ -274,10 +275,11 @@ Else Log(ex, "播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & ")", LogLevel.Hint) End If - Log(ex, "播放音乐失败(" & MusicCurrent & ")", LogLevel.Developer) - If MusicAllList.Count > 1 Then - Thread.Sleep(1000) - MusicStartPlay(DequeueNextMusicAddress, IsFirstLoad) + Thread.Sleep(1000) + If TypeOf ex Is FileNotFoundException Then + MusicRefreshPlay(True, IsFirstLoad) + ElseIf MusicAllList.Count > 1 Then + MusicStartPlay(DequeueNextMusicAddress(), IsFirstLoad) End If Finally If CurrentWave IsNot Nothing Then CurrentWave.Dispose() diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index 226ee1a..147e981 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 31b8124..3c51c07 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -190,7 +190,7 @@ Public Module ModDownloadLib Logo = "pack://application:,,,/images/Blocks/CobbleStone.png" End Select '建立控件 - Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Entry("id").ToString, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60} + Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Entry("id").ToString, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry} If Entry("lore") Is Nothing Then NewItem.Info = Entry("releaseTime").Value(Of Date).ToString("yyyy/MM/dd HH:mm") Else @@ -718,7 +718,7 @@ Retry: Public Function OptiFineDownloadListItem(Entry As DlOptiFineListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem '建立控件 Dim NewItem As New MyListItem With { - .Title = Entry.NameDisplay, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60, + .Title = Entry.NameDisplay, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .Info = If(Entry.IsPreview, "测试版", "正式版") & If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime) & If(Entry.RequiredForgeVersion Is Nothing, ",不兼容 Forge", If(Entry.RequiredForgeVersion = "", "", ",推荐 Forge 版本:" & Entry.RequiredForgeVersion)), @@ -844,17 +844,17 @@ Retry: '老版本 Select Case DownloadInfo.Inherit Case "1.7.10" - Address.Add("http://dl.liteloader.com/redist/1.7.10/liteloader-installer-1.7.10-04.jar") + Address.Add("https://dl.liteloader.com/redist/1.7.10/liteloader-installer-1.7.10-04.jar") Case "1.7.2" - Address.Add("http://dl.liteloader.com/redist/1.7.2/liteloader-installer-1.7.2-04.jar") + Address.Add("https://dl.liteloader.com/redist/1.7.2/liteloader-installer-1.7.2-04.jar") Case "1.6.4" - Address.Add("http://dl.liteloader.com/redist/1.6.4/liteloader-installer-1.6.4-01.jar") + Address.Add("https://dl.liteloader.com/redist/1.6.4/liteloader-installer-1.6.4-01.jar") Case "1.6.2" - Address.Add("http://dl.liteloader.com/redist/1.6.2/liteloader-installer-1.6.2-04.jar") + Address.Add("https://dl.liteloader.com/redist/1.6.2/liteloader-installer-1.6.2-04.jar") Case "1.5.2" 'Address.AddRange({"https://download.mcbbs.net/maven/com/mumfrey/liteloader/1.5.2/liteloader-1.5.2.jar", ' "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/1.5.2/liteloader-1.5.2.jar"}) - Address.Add("http://dl.liteloader.com/redist/1.5.2/liteloader-installer-1.5.2-01.jar") + Address.Add("https://dl.liteloader.com/redist/1.5.2/liteloader-installer-1.5.2-01.jar") Case Else Throw New NotSupportedException("未知的 Minecraft 版本(" & DownloadInfo.Inherit & ")") End Select @@ -921,7 +921,7 @@ Retry: VersionJson.Add("type", "release") VersionJson.Add("arguments", GetJson("{""game"":[""--tweakClass"",""" & DownloadInfo.JsonToken("tweakClass").ToString & """]}")) VersionJson.Add("libraries", DownloadInfo.JsonToken("libraries")) - CType(VersionJson("libraries"), JContainer).Add(GetJson("{""name"": ""com.mumfrey:liteloader:" & DownloadInfo.JsonToken("version").ToString & """,""url"": ""http://dl.liteloader.com/versions/""}")) + CType(VersionJson("libraries"), JContainer).Add(GetJson("{""name"": ""com.mumfrey:liteloader:" & DownloadInfo.JsonToken("version").ToString & """,""url"": ""https://dl.liteloader.com/versions/""}")) VersionJson.Add("mainClass", "net.minecraft.launchwrapper.Launch") VersionJson.Add("minimumLauncherVersion", 18) VersionJson.Add("inheritsFrom", DownloadInfo.Inherit) @@ -949,7 +949,7 @@ Retry: Public Function LiteLoaderDownloadListItem(Entry As DlLiteLoaderListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem '建立控件 Dim NewItem As New MyListItem With { - .Title = Entry.Inherit, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 30, + .Title = Entry.Inherit, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .Info = If(Entry.IsPreview, "测试版", "稳定版") & If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime), .Logo = "pack://application:,,,/images/Blocks/Egg.png" } @@ -965,10 +965,8 @@ Retry: End Function Private Sub LiteLoaderSaveContMenuBuild(sender As MyListItem, e As EventArgs) If sender.Tag.IsLegacy Then - sender.PaddingRight = 0 sender.Buttons = {} Else - sender.PaddingRight = 30 Dim BtnList As New MyIconButton With {.Logo = Logo.IconButtonList, .ToolTip = "查看全部版本", .Tag = sender} ToolTipService.SetPlacement(BtnList, Primitives.PlacementMode.Center) ToolTipService.SetVerticalOffset(BtnList, 30) @@ -984,10 +982,8 @@ Retry: ToolTipService.SetHorizontalOffset(BtnSave, 2) AddHandler BtnSave.Click, AddressOf LiteLoaderSave_Click If sender.Tag.IsLegacy Then - sender.PaddingRight = 30 sender.Buttons = {BtnSave} Else - sender.PaddingRight = 60 Dim BtnList As New MyIconButton With {.Logo = Logo.IconButtonList, .ToolTip = "查看全部版本", .Tag = sender} ToolTipService.SetPlacement(BtnList, Primitives.PlacementMode.Center) ToolTipService.SetVerticalOffset(BtnList, 30) @@ -1003,7 +999,7 @@ Retry: Else Version = sender.Tag.Tag End If - OpenWebsite("http://jenkins.liteloader.com/view/" & Version.Inherit) + OpenWebsite("https://jenkins.liteloader.com/view/" & Version.Inherit) End Sub Public Sub LiteLoaderSave_Click(sender As Object, e As RoutedEventArgs) 'ListItem 与小按钮都会调用这个方法 @@ -1107,7 +1103,7 @@ Retry: Files.Add(New NetFile({ "https://download.mcbbs.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName, "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName, - "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName + "https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName }, Target, New FileChecker(MinSize:=64 * 1024, Hash:=DownloadInfo.Hash))) Task.Output = Files End Sub) With {.ProgressWeight = 0.1, .Show = False}) @@ -1326,7 +1322,7 @@ Retry: '添加主文件 Dim Files As New List(Of NetFile) From {New NetFile({ "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName, - "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName, + "https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName, "https://download.mcbbs.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName }, InstallerAddress, New FileChecker(MinSize:=64 * 1024, Hash:=DownloadInfo.Hash))} Task.Output = Files @@ -1580,7 +1576,7 @@ Retry: Public Function ForgeDownloadListItem(Entry As DlForgeVersionEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem '建立控件 Dim NewItem As New MyListItem With { - .Title = Entry.Version, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60, + .Title = Entry.Version, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .Info = If(Entry.ReleaseTime = "", If(ModeDebug, "种类:" & Entry.Category & If(Entry.Branch Is Nothing, "", ",开发分支:" & Entry.Branch), ""), "发布于 " & Entry.ReleaseTime & If(ModeDebug, ",种类:" & Entry.Category & If(Entry.Branch Is Nothing, "", ",开发分支:" & Entry.Branch), "")), @@ -1626,7 +1622,7 @@ Retry: Else Version = sender.Parent.Parent.Tag End If - OpenWebsite("http://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Version.Inherit & "-" & Version.Version & "/forge-" & Version.Inherit & "-" & Version.Version & "-changelog.txt") + OpenWebsite("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Version.Inherit & "-" & Version.Version & "/forge-" & Version.Inherit & "-" & Version.Version & "-changelog.txt") End Sub Public Sub ForgeSave_Click(sender As Object, e As RoutedEventArgs) Dim Version As DlForgeVersionEntry @@ -1787,7 +1783,7 @@ Retry: Public Function FabricDownloadListItem(Entry As JObject, OnClick As MyListItem.ClickEventHandler) As MyListItem '建立控件 Dim NewItem As New MyListItem With { - .Title = Entry("version").ToString.Replace("+build", ""), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60, + .Title = Entry("version").ToString.Replace("+build", ""), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .Info = If(Entry("stable").ToObject(Of Boolean), "稳定版", "测试版"), .Logo = "pack://application:,,,/images/Blocks/Fabric.png" } @@ -1798,7 +1794,7 @@ Retry: Public Function FabricApiDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem '建立控件 Dim NewItem As New MyListItem With { - .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").Split("+").First.Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60, + .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").Split("+").First.Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy/MM/dd HH:mm"), .Logo = "pack://application:,,,/images/Blocks/Fabric.png" } @@ -1809,7 +1805,7 @@ Retry: Public Function OptiFabricDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem '建立控件 Dim NewItem As New MyListItem With { - .Title = Entry.DisplayName.ToLower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .PaddingRight = 60, + .Title = Entry.DisplayName.ToLower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy/MM/dd HH:mm"), .Logo = "pack://application:,,,/images/Blocks/OptiFabric.png" } diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb index 291aa3f..b113f75 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb @@ -1,6 +1,5 @@ Public Class PageDownloadCompDetail Private CompItem As MyCompItem = Nothing - Private Project As CompProject #Region "加载器" @@ -10,8 +9,12 @@ '初始化加载器信息 Private Sub PageDownloadCompDetail_Inited(sender As Object, e As EventArgs) Handles Me.Initialized Project = FrmMain.PageCurrent.Additional(0) + TargetVersion = FrmMain.PageCurrent.Additional(2) + TargetLoader = FrmMain.PageCurrent.Additional(3) PageLoaderInit(Load, PanLoad, PanMain, CardIntro, CompFileLoader, AddressOf Load_OnFinish) End Sub + Private Project As CompProject + Private TargetVersion As String, TargetLoader As CompModLoaderType '自动重试 Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged Select Case CompFileLoader.State @@ -39,23 +42,8 @@ End Sub End Class Private Sub Load_OnFinish() - '获取目标版本与 Mod 加载器 - Dim TargetVersion As String - Select Case Project.Type - Case CompType.Mod - TargetVersion = If(PageDownloadMod.Loader.Input.GameVersion, "") - Case CompType.ModPack - TargetVersion = If(PageDownloadPack.Loader.Input.GameVersion, "") - Case Else 'CompType.ResourcePack - 'FUTURE: Res - TargetVersion = "" 'If(PageDownloadResource.Loader.Input.GameVersion, "") - End Select - Dim TargetLoader = CompModLoaderType.Any - If Project.Type = CompType.Mod AndAlso PageDownloadMod.Loader.Input.ModLoader <> CompModLoaderType.Any Then - TargetLoader = PageDownloadMod.Loader.Input.ModLoader - End If Dim TargetCardName As String = If(TargetVersion <> "" OrElse TargetLoader <> CompModLoaderType.Any, - $"目标版本:{TargetVersion} {If(TargetLoader <> CompModLoaderType.Any, TargetLoader, "")}", "") + $"所选版本:{TargetVersion} {If(TargetLoader <> CompModLoaderType.Any, TargetLoader, "")}", "") '初始化字典 Dim Dict As New SortedDictionary(Of String, List(Of CompFile))(New VersionSorterWithSelect(TargetCardName)) Dict.Add("未知版本", New List(Of CompFile)) @@ -144,8 +132,7 @@ '放置当前工程 If CompItem IsNot Nothing Then PanIntro.Children.Remove(CompItem) CompItem = Project.ToCompItem(True, True) - CompItem.IsHitTestVisible = False - CompItem.HasAnimation = False + CompItem.CanInteraction = False CompItem.Margin = New Thickness(-7, -7, 0, 8) PanIntro.Children.Insert(0, CompItem) diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml index 35642d7..98c221b 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml @@ -26,7 +26,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml index 491c058..da78a28 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml @@ -5,7 +5,7 @@ - @@ -13,10 +13,10 @@ - - @@ -25,7 +25,7 @@ - @@ -34,7 +34,7 @@ - @@ -43,7 +43,7 @@ - @@ -51,7 +51,7 @@ - @@ -60,7 +60,7 @@ - @@ -68,7 +68,7 @@ - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb index f8ff085..da173ad 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb @@ -99,11 +99,13 @@ Case FormMain.PageSubType.DownloadMod PageDownloadMod.Storage = New CompProjectStorage PageDownloadMod.Page = 0 + CompProjectCache.Clear() If FrmDownloadMod IsNot Nothing Then FrmDownloadMod.PageLoaderRestart() ItemMod.Checked = True Case FormMain.PageSubType.DownloadPack PageDownloadPack.Storage = New CompProjectStorage PageDownloadPack.Page = 0 + CompProjectCache.Clear() If FrmDownloadPack IsNot Nothing Then FrmDownloadPack.PageLoaderRestart() ItemPack.Checked = True Case FormMain.PageSubType.DownloadClient diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml index f42b739..6aab1a8 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml @@ -33,6 +33,7 @@ + @@ -97,6 +98,7 @@ + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb index 02872ae..4fff1a6 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb @@ -46,6 +46,13 @@ BtnPageRight.IsEnabled = Storage.Results.Count > PageSize * (Page + 1) OrElse Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal BtnPageRight.Opacity = If(BtnPageRight.IsEnabled, 1, 0.2) + '错误信息 + If Storage.ErrorMessage Is Nothing Then + HintError.Visibility = Visibility.Collapsed + Else + HintError.Visibility = Visibility.Visible + HintError.Text = Storage.ErrorMessage + End If '强制返回顶部 PanBack.ScrollToTop() Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml index db1460b..c3e3cc4 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml @@ -32,6 +32,7 @@ + @@ -87,6 +88,7 @@ + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb index bb3eb66..d9cbd06 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb @@ -45,6 +45,13 @@ BtnPageRight.IsEnabled = Storage.Results.Count > PageSize * (Page + 1) OrElse Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal BtnPageRight.Opacity = If(BtnPageRight.IsEnabled, 1, 0.2) + '错误信息 + If Storage.ErrorMessage Is Nothing Then + HintError.Visibility = Visibility.Collapsed + Else + HintError.Visibility = Visibility.Visible + HintError.Text = Storage.ErrorMessage + End If '强制返回顶部 PanBack.ScrollToTop() Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb index e26a226..6ee13ca 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb @@ -236,7 +236,8 @@ Retry: {"Migrator", "迁移者披风"}, {"MapMaker", "Realms 地图制作者披风"}, {"Moderator", "Mojira 管理员披风"}, {"Translator-Chinese", "Crowdin 中文翻译者披风"}, {"Translator", "Crowdin 翻译者披风"}, {"Cobalt", "Cobalt 披风"}, {"Vanilla", "原版披风"}, {"Minecon2011", "Minecon 2011 参与者披风"}, {"Minecon2012", "Minecon 2012 参与者披风"}, - {"Minecon2013", "Minecon 2013 参与者披风"}, {"Minecon2015", "Minecon 2015 参与者披风"}, {"Minecon2016", "Minecon 2016 参与者披风"} + {"Minecon2013", "Minecon 2013 参与者披风"}, {"Minecon2015", "Minecon 2015 参与者披风"}, {"Minecon2016", "Minecon 2016 参与者披风"}, + {"Cherry Blossom Cape", "樱花披风"}, {"CherryBlossomCape", "樱花披风"} } Dim SelectionControl As New List(Of IMyRadio) From {New MyRadioBox With {.Text = "无披风"}} For Each Cape In SkinData("capes") diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb index 55e07e6..8a51c7d 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb @@ -92,6 +92,10 @@ Download: Log("[Page] 主页预设:简单主页") Url = "https://cdn.wearclub.online/PCL/Custom.xaml" GoTo Download + Case 4 '历史上的今天 + Log("[Page] 主页预设:历史上的今天") + Url = "https://pclnews.52chye.cn/custom.xaml" + GoTo Download End Select End Select RunInUi(Sub() LoadContent(Content)) diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml index b2a0919..c7e5d97 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml @@ -14,23 +14,23 @@ Margin="0,0,0,6" /> + Logo="pack://application:,,,/images/Blocks/Grass.png" /> + Logo="pack://application:,,,/images/Blocks/Grass.png" /> + Logo="pack://application:,,,/images/Blocks/Grass.png" /> + Logo="pack://application:,,,/images/Blocks/RedstoneBlock.png" /> diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb index eec5ebe..656ac59 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb @@ -197,7 +197,7 @@ Reopen: GoTo FinishHiperFileCheck End If Log("[Hiper] hiper.exe 的所需 SHA1:" & ChecksumHiper) - If ChecksumHiper = GetAuthSHA1(PathHiper & "HiPer 联机模块.exe") Then + If ChecksumHiper = GetFileSHA1(PathHiper & "HiPer 联机模块.exe") Then Log("[Hiper] hiper.exe 文件校验通过,无需重新下载") GoTo FinishHiperFileCheck End If @@ -233,7 +233,7 @@ FinishHiperFileCheck: GoTo FinishWintunFileCheck End If Log("[Hiper] wintun.dll 的所需 SHA1:" & ChecksumWintun) - If ChecksumWintun = GetAuthSHA1(PathHiper & "wintun.dll") Then + If ChecksumWintun = GetFileSHA1(PathHiper & "wintun.dll") Then Log("[Hiper] wintun.dll 文件校验通过,无需重新下载") GoTo FinishWintunFileCheck End If @@ -269,7 +269,7 @@ FinishWintunFileCheck: GoTo FinishMcbFileCheck End If Log("[Hiper] mcb.exe 的所需 SHA1:" & ChecksumMcb) - If ChecksumMcb = GetAuthSHA1(PathHiper & "MCB 联机模块.exe") Then + If ChecksumMcb = GetFileSHA1(PathHiper & "MCB 联机模块.exe") Then Log("[Hiper] mcb.exe 文件校验通过,无需重新下载") GoTo FinishMcbFileCheck End If diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb index 8d7a317..6fad614 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb @@ -851,7 +851,6 @@ Done: Public Function ToListItem() As MyListItem Dim Item As New MyListItem With { .Title = DisplayName, .Height = 42, .Tag = Me, .Type = MyListItem.CheckType.None, - .PaddingRight = 60, .Logo = "pack://application:,,,/images/Blocks/Grass.png"} '绑定图标按钮 Dim BtnRefresh As New MyIconButton With {.Logo = Logo.IconButtonRefresh, .LogoScale = 0.85, .ToolTip = "刷新", .Tag = Me} @@ -941,7 +940,7 @@ Done: End Function Public Function ToListItem() As MyListItem Dim Item As New MyListItem With { - .Title = DisplayName, .Height = 42, .Info = GetDescription(), .Tag = Me, .PaddingRight = If(IsOwner, 60, 0), + .Title = DisplayName, .Height = 42, .Info = GetDescription(), .Tag = Me, .Type = If(IsOwner, MyListItem.CheckType.None, MyListItem.CheckType.Clickable), .Logo = "pack://application:,,,/images/Blocks/" & If(IsOwner, "GrassPath", "Grass") & ".png"} If IsOwner Then diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml index d599911..bf07b19 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml @@ -7,7 +7,7 @@ mc:Ignorable="d" AnimatedControl="{Binding ElementName=PanItem}" MinWidth="150"> - @@ -17,7 +17,7 @@ - @@ -28,17 +28,17 @@ - - + - - diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml index bf1e472..119a07c 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}"> - @@ -11,13 +11,13 @@ - - - - diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb index 3dba754..66a8d42 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb @@ -71,7 +71,7 @@ CType(ContMenu.FindName("Rename"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Rename_Click)) CType(ContMenu.FindName("Refresh"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Refresh_Click)) '构建框架与图表按钮 - Dim NewItem As New MyListItem With {.IsScaleAnimationEnabled = False, .Type = MyListItem.CheckType.RadioBox, .PaddingRight = 30, .Title = Folder.Name, .Info = Folder.Path, .Height = 40, .ContextMenu = ContMenu, .Tag = Folder} + Dim NewItem As New MyListItem With {.IsScaleAnimationEnabled = False, .Type = MyListItem.CheckType.RadioBox, .MinPaddingRight = 30, .Title = Folder.Name, .Info = Folder.Path, .Height = 40, .ContextMenu = ContMenu, .Tag = Folder} AddHandler NewItem.Changed, AddressOf FrmSelectLeft.Folder_Change Dim NewIconButton As New MyIconButton With {.Logo = Logo.IconButtonSetup, .LogoScale = 1.1} AddHandler NewIconButton.Click, Sub(sender, e) diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb index 215b136..1709a4a 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb @@ -111,7 +111,7 @@ End Try End Sub Public Shared Function McVersionListItem(Version As McVersion) As MyListItem - Dim NewItem As New MyListItem With {.Title = Version.Name, .Info = Version.Info, .Height = 42, .Tag = Version, .PaddingRight = 78, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable} + Dim NewItem As New MyListItem With {.Title = Version.Name, .Info = Version.Info, .Height = 42, .Tag = Version, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable} Try If Version.Logo.EndsWith("PCL\Logo.png") Then NewItem.Logo = Version.Path & "PCL\Logo.png" '修复老版本中,存储的自定义 Logo 使用完整路径,导致移动后无法加载的 Bug diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml index c7319cd..795f41e 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml @@ -4,34 +4,34 @@ xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageSetupLeft" d:DesignWidth="152" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}"> - - + - + - + - - + - - + diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml index b53b0f3..32d8fde 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml @@ -3,17 +3,17 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}"> - - - + - @@ -21,7 +21,7 @@ - diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb index 4059675..9f33fed 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb @@ -89,7 +89,14 @@ Public Sub Refresh(sender As Object, e As EventArgs) '由边栏按钮匿名调用 '强制刷新 - If FrmVersionMod IsNot Nothing Then FrmVersionMod.RefreshList(True) '没有 Else,还没加载刷个鬼的新 + Try + CompProjectCache.Clear() + File.Delete(PathTemp & "Cache\LocalMod.json") + Log("[Mod] 由于点击刷新按钮,清理本地 Mod 信息缓存") + Catch ex As Exception + Log(ex, "强制刷新时清理本地 Mod 信息缓存失败") + End Try + If FrmVersionMod IsNot Nothing Then FrmVersionMod.RefreshList(True) '无需 Else,还没加载刷个鬼的新 ItemMod.Checked = True Hint("正在刷新……", Log:=False) End Sub diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml index 2df274d..6f37548 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml @@ -9,23 +9,21 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + - diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb index 686fa06..18a5492 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb @@ -67,7 +67,7 @@ SearchBox.Text = "" '建立 StackPanel - 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)} + PanItems = New StackPanel With {.Margin = New Thickness(20, 38, 18, If(List.Count > 0, 20, 0)), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0)} For Each ModEntity As McMod In List PanItems.Children.Add(McModListItem(ModEntity)) Next @@ -95,63 +95,20 @@ 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 - '图标 - Dim Logo As String - Select Case Entry.State - Case McMod.McModState.Fine - Logo = "pack://application:,,,/images/Blocks/RedstoneLampOn.png" - Case McMod.McModState.Disabled - Logo = "pack://application:,,,/images/Blocks/RedstoneLampOff.png" - Case Else '出错 - Logo = "pack://application:,,,/images/Blocks/RedstoneBlock.png" - End Select - '文本 - Dim Title As String - If Entry.State = McMod.McModState.Disabled Then - Title = GetFileNameWithoutExtentionFromPath(Entry.Path.Substring(0, Entry.Path.Count - ".disabled".Count)) & "(已禁用)" - Else - Title = GetFileNameWithoutExtentionFromPath(Entry.Path) - End If - Dim Desc As String - If Entry.Version Is Nothing OrElse Entry.Description IsNot Nothing Then - Desc = Entry.Name & If(Entry.Version Is Nothing, "", " (" & Entry.Version & ")") & " : " & If(Entry.Description, Entry.Path) - ElseIf Entry.IsFileAvailable Then - Desc = Entry.Path - Else - Desc = "存在错误 : " & Entry.Path - End If - 'Desc = If(Entry.ModId, "无可用名称") & " (" & If(Entry.Version, "无可用版本") & ")" - 'If Entry.Dependencies.Count > 0 Then - ' Dim DepList As New List(Of String) - ' For Each Dep In Entry.Dependencies - ' DepList.Add(Dep.Key & If(Dep.Value Is Nothing, "", "@" & Dep.Value)) - ' Next - ' Desc += " : " & Join(DepList, ", ") - 'End If - '实例化 + Private Function McModListItem(Entry As McMod) As MyLocalModItem 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) - } + Dim NewItem As New MyLocalModItem With {.SnapsToDevicePixels = True, .Entry = Entry, + .ButtonHandler = AddressOf McModContent, .Checked = SelectedMods.Contains(Entry.RawFileName)} + AddHandler Entry.OnGetCompProject, AddressOf NewItem.Refresh + NewItem.Refresh() AniControlEnabled -= 1 Return NewItem End Function - Private Sub McModContent(sender As MyListItem, e As EventArgs) - Dim ModEntity As McMod = sender.Tag - '注册点击事件 + Private Sub McModContent(sender As MyLocalModItem, e As EventArgs) + '点击事件 AddHandler sender.Changed, AddressOf CheckChanged - AddHandler sender.LogoClick, AddressOf ItemLogo_Click + AddHandler sender.Click, Sub(ss As MyLocalModItem, ee As EventArgs) ss.Checked = Not ss.Checked '图标按钮 - Dim BtnDel As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonDelete, .Tag = sender} - BtnDel.ToolTip = "删除" - ToolTipService.SetPlacement(BtnDel, Primitives.PlacementMode.Center) - ToolTipService.SetVerticalOffset(BtnDel, 30) - ToolTipService.SetHorizontalOffset(BtnDel, 2) - AddHandler BtnDel.Click, AddressOf Delete_Click Dim BtnOpen As New MyIconButton With {.LogoScale = 1.15, .Logo = Logo.IconButtonOpen, .Tag = sender} BtnOpen.ToolTip = "打开文件位置" ToolTipService.SetPlacement(BtnOpen, Primitives.PlacementMode.Center) @@ -165,7 +122,7 @@ ToolTipService.SetHorizontalOffset(BtnCont, 2) AddHandler BtnCont.Click, AddressOf Info_Click AddHandler sender.MouseRightButtonDown, AddressOf Info_Click - sender.Buttons = {BtnCont, BtnOpen, BtnDel} + sender.Buttons = {BtnCont, BtnOpen} End Sub #End Region @@ -206,7 +163,11 @@ ''' 全选。 ''' Private Sub BtnManageSelectAll_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageSelectAll.Click - ChangeAllSelected(True) + If SelectedMods.Count < If(IsSearching, PanSearchList.Children.Count, McModLoader.Output.Count) Then + ChangeAllSelected(True) + Else + ChangeAllSelected(False) + End If End Sub #End Region @@ -217,10 +178,10 @@ Public SelectedMods As New List(Of String) '单项切换选择状态 - Public Sub CheckChanged(sender As MyListItem, e As RouteEventArgs) + Public Sub CheckChanged(sender As MyLocalModItem, e As RouteEventArgs) If AniControlEnabled <> 0 Then Return '更新选择了的内容 - Dim SelectedKey As String = CType(sender.Tag, McMod).RawFileName + Dim SelectedKey As String = sender.Entry.RawFileName If sender.Checked Then If Not SelectedMods.Contains(SelectedKey) Then SelectedMods.Add(SelectedKey) Else @@ -304,13 +265,13 @@ SelectedMods.Clear() If IsSearching Then '搜索中 - For Each Item As MyListItem In PanSearchList.Children + For Each Item As MyLocalModItem In PanSearchList.Children Item.Checked = Value - If Value Then SelectedMods.Add(CType(Item.Tag, McMod).RawFileName) + If Value Then SelectedMods.Add(Item.Entry.RawFileName) Next If Not Value Then '只取消选择 If PanItems IsNot Nothing Then - For Each Item As MyListItem In PanItems.Children + For Each Item As MyLocalModItem In PanItems.Children Item.Checked = Value Next End If @@ -318,9 +279,9 @@ Else '非搜索中 If PanItems IsNot Nothing Then - For Each Item As MyListItem In PanItems.Children + For Each Item As MyLocalModItem In PanItems.Children Item.Checked = Value - If Value Then SelectedMods.Add(CType(Item.Tag, McMod).RawFileName) + If Value Then SelectedMods.Add(Item.Entry.RawFileName) Next End If End If @@ -372,11 +333,12 @@ End Try '更改 Loader 和 UI 中的列表 Dim NewModEntity As New McMod(NewPath) + NewModEntity.Comp = ModEntity.Comp Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity) McModLoader.Output.RemoveAt(IndexOfLoader) McModLoader.Output.Insert(IndexOfLoader, NewModEntity) Dim List = If(IsSearching, PanSearchList, PanItems).Children - Dim IndexOfUi As Integer = List.IndexOf(List.OfType(Of MyListItem).FirstOrDefault(Function(i) CType(i.Tag, McMod) Is ModEntity)) + Dim IndexOfUi As Integer = List.IndexOf(List.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry Is ModEntity)) If IndexOfUi = -1 Then Continue For '因为未知原因 Mod 的状态已经切换完了 List.RemoveAt(IndexOfUi) List.Insert(IndexOfUi, McModListItem(NewModEntity)) @@ -419,7 +381,7 @@ '更改 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)) + Dim IndexOfUi As Integer = Parent.Children.IndexOf(Parent.Children.OfType(Of MyLocalModItem).First(Function(i) i.Entry Is ModEntity)) Parent.Children.RemoveAt(IndexOfUi) Next If Not IsSearching Then @@ -454,165 +416,69 @@ #Region "单个 Mod 项" - '点击 Logo:切换可用状态 / 显示错误原因 - Public Sub ItemLogo_Click(sender As MyListItem, e As EventArgs) - Try - - Dim ModEntity As McMod = sender.Tag - Dim NewPath As String = Nothing - Select Case ModEntity.State - Case McMod.McModState.Fine - '前置检测警告 - If ModEntity.IsPresetMod Then - If MyMsgBox("该 Mod 可能为其他 Mod 的前置,如果禁用可能导致其他 Mod 无法使用。" & vbCrLf & "你确定要继续禁用吗?", "警告", "禁用", "取消") = 2 Then Exit Sub - End If - NewPath = ModEntity.Path & ".disabled" - Case McMod.McModState.Disabled - NewPath = ModEntity.Path.Substring(0, ModEntity.Path.Count - ".disabled".Count) - Case McMod.McModState.Unavaliable - MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntity.FileUnavailableReason), "Mod 读取失败") - Exit Sub - End Select - '重命名 - Dim NewModEntity As New McMod(NewPath) - Try - File.Delete(NewPath) - FileSystem.Rename(ModEntity.Path, NewPath) - Catch ex As FileNotFoundException - Log(ex, "未找到理应存在的 Mod 文件(" & ModEntity.Path & ")") - FileSystem.Rename(NewPath, ModEntity.Path) - NewModEntity = New McMod(ModEntity.Path) - End Try - '更改 Loader 中的列表 - Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity) - McModLoader.Output.RemoveAt(IndexOfLoader) - McModLoader.Output.Insert(IndexOfLoader, NewModEntity) - '更改 UI 中的列表 - Dim Parent As StackPanel = sender.Parent - Dim IndexOfUi As Integer = Parent.Children.IndexOf(sender) - Parent.Children.RemoveAt(IndexOfUi) - Parent.Children.Insert(IndexOfUi, McModListItem(NewModEntity)) - '仅在非搜索页面才执行,以确保搜索页内外显示一致 - 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 失败") - Hint("切换 Mod 状态失败,请尝试关闭正在运行的游戏后再试!") - LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly) - End Try - End Sub - '删除 - Public Sub Delete_Click(sender As MyIconButton, e As EventArgs) - Try - - Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown - Dim ListItem As MyListItem = sender.Tag - Dim ModEntity As McMod = ListItem.Tag - '前置检测警告 - If ModEntity.IsPresetMod Then - If MyMsgBox("该 Mod 可能为其他 Mod 的前置,如果删除可能导致其他 Mod 无法使用。" & vbCrLf & "你确定要继续删除吗?", "警告", "删除", "取消", IsWarn:=True) = 2 Then Exit Sub - End If - '删除 - If File.Exists(ModEntity.Path) Then - If IsShiftPressed Then - File.Delete(ModEntity.Path) - Else - My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) - End If - Else - '在不明原因下删除的文件可能还在列表里,如果玩家又点了一次就会出错,总之加个判断也不碍事 - Log("[System] 需要删除的 Mod 文件不存在(" & ModEntity.Path & ")", LogLevel.Hint) - Exit Sub - End If - '更改 Loader 中的列表 - McModLoader.Output.Remove(ModEntity) - '更改 UI 中的列表 - Dim Parent As StackPanel = ListItem.Parent - Parent.Children.Remove(ListItem) - If Parent.Children.Count = 0 Then - RefreshList(True) - Else - CType(Parent.Parent, MyCard).Title = McModGetTitle(McModLoader.Output) - End If - '显示提示 - If IsShiftPressed Then - Hint("已删除 " & ModEntity.FileName & "!", HintType.Finish) - Else - Hint("已将 " & ModEntity.FileName & " 删除到回收站!", HintType.Finish) - End If - LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.UpdateOnly) - - Catch ex As OperationCanceledException - Log(ex, "删除 Mod 被主动取消") - Catch ex As Exception - Log(ex, "删除 Mod 失败", LogLevel.Feedback) - End Try - End Sub '详情 Public Sub Info_Click(sender As Object, e As EventArgs) Try - Dim ListItem As MyListItem - ListItem = If(TypeOf sender Is MyIconButton, sender.Tag, sender) - Dim ModEntity As McMod = ListItem.Tag + Dim ModEntry As McMod = CType(If(TypeOf sender Is MyIconButton, sender.Tag, sender), MyLocalModItem).Entry '加载失败信息 - If ModEntity.State = McMod.McModState.Unavaliable Then - MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntity.FileUnavailableReason), "Mod 读取失败") + If ModEntry.State = McMod.McModState.Unavaliable Then + MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntry.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) - 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(" ", "+") - Dim ModSearchName As String = ModOriginalName.Substring(0, 1) - For i = 1 To ModOriginalName.Count - 1 - Dim IsLastLower As Boolean = ModOriginalName(i - 1).ToString.ToLower.Equals(ModOriginalName(i - 1).ToString) - Dim IsCurrentLower As Boolean = ModOriginalName(i).ToString.ToLower.Equals(ModOriginalName(i).ToString) - If IsLastLower AndAlso Not IsCurrentLower Then - '上一个字母为小写,这一个字母为大写 - ModSearchName += "+" - End If - ModSearchName += ModOriginalName(i) - Next - ModSearchName = ModSearchName.Replace("++", "+").Replace("pti+Fine", "ptiFine") - '显示 - If ModEntity.Url Is Nothing Then - If MyMsgBox(Join(ContentLines, vbCrLf), ModEntity.Name, "百科搜索", "返回") = 1 Then - OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0") - End If + If ModEntry.Comp IsNot Nothing Then + '跳转到 Mod 下载页面 + FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail, + .Additional = {ModEntry.Comp, New List(Of String), PageVersionLeft.Version.Version.McName, + If(PageVersionLeft.Version.Version.HasForge, CompModLoaderType.Forge, If(PageVersionLeft.Version.Version.HasFabric, CompModLoaderType.Fabric, CompModLoaderType.Any))}}) Else - Select Case MyMsgBox(Join(ContentLines, vbCrLf), ModEntity.Name, "打开官网", "百科搜索", "返回") - Case 1 - OpenWebsite(ModEntity.Url) - Case 2 + '获取信息 + Dim ContentLines As New List(Of String) + If ModEntry.Description IsNot Nothing Then ContentLines.Add(ModEntry.Description & vbCrLf) + If ModEntry.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntry.Authors) + ContentLines.Add("文件:" & ModEntry.FileName & "(" & GetString(New FileInfo(ModEntry.Path).Length) & ")") + If ModEntry.Version IsNot Nothing Then ContentLines.Add("版本:" & ModEntry.Version) + Dim DebugInfo As New List(Of String) + If ModEntry.ModId IsNot Nothing Then + DebugInfo.Add("Mod ID:" & ModEntry.ModId) + End If + If ModEntry.Dependencies.Count > 0 Then + DebugInfo.Add("依赖于:") + For Each Dep In ModEntry.Dependencies + DebugInfo.Add(" - " & Dep.Key & If(Dep.Value Is Nothing, "", ",版本:" & Dep.Value)) + Next + End If + If DebugInfo.Count > 0 Then + ContentLines.Add("") + ContentLines.AddRange(DebugInfo) + End If + '获取用于搜索的 Mod 名称 + Dim ModOriginalName As String = ModEntry.Name.Replace(" ", "+") + Dim ModSearchName As String = ModOriginalName.Substring(0, 1) + For i = 1 To ModOriginalName.Count - 1 + Dim IsLastLower As Boolean = ModOriginalName(i - 1).ToString.ToLower.Equals(ModOriginalName(i - 1).ToString) + Dim IsCurrentLower As Boolean = ModOriginalName(i).ToString.ToLower.Equals(ModOriginalName(i).ToString) + If IsLastLower AndAlso Not IsCurrentLower Then + '上一个字母为小写,这一个字母为大写 + ModSearchName += "+" + End If + ModSearchName += ModOriginalName(i) + Next + ModSearchName = ModSearchName.Replace("++", "+").Replace("pti+Fine", "ptiFine") + '显示 + If ModEntry.Url Is Nothing Then + If MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "百科搜索", "返回") = 1 Then OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0") - End Select + End If + Else + Select Case MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "打开官网", "百科搜索", "返回") + Case 1 + OpenWebsite(ModEntry.Url) + Case 2 + OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0") + End Select + End If End If - Catch ex As Exception Log(ex, "获取 Mod 详情失败", LogLevel.Feedback) End Try @@ -621,9 +487,8 @@ Public Sub Open_Click(sender As MyIconButton, e As EventArgs) Try - Dim ListItem As MyListItem = sender.Tag - Dim ModEntity As McMod = ListItem.Tag - OpenExplorer("/select,""" & ModEntity.Path & """") + Dim ListItem As MyLocalModItem = sender.Tag + OpenExplorer("/select,""" & ListItem.Entry.Path & """") Catch ex As Exception Log(ex, "打开 Mod 文件位置失败", LogLevel.Feedback) @@ -632,6 +497,8 @@ #End Region +#Region "搜索" + Public ReadOnly Property IsSearching As Boolean Get Return Not String.IsNullOrWhiteSpace(SearchBox.Text) @@ -643,17 +510,22 @@ '构造请求 Dim QueryList As New List(Of SearchEntry(Of McMod)) For Each Entry As McMod In McModLoader.Output - QueryList.Add(New SearchEntry(Of McMod) With { - .Item = Entry, - .SearchSource = New List(Of KeyValuePair(Of String, Double)) From { - New KeyValuePair(Of String, Double)(Entry.Name, 1), - New KeyValuePair(Of String, Double)(Entry.FileName, 1), - New KeyValuePair(Of String, Double)(If(Entry.Description, ""), 0.5) - } - }) + Dim SearchSource As New List(Of KeyValuePair(Of String, Double)) + SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Name, 1)) + SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.FileName, 1)) + If Entry.Version IsNot Nothing Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Version, 0.2)) + If Entry.Description IsNot Nothing AndAlso Entry.Description <> "" Then + SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Description, 0.4)) + End If + If Entry.Comp IsNot Nothing Then + If Entry.Comp.RawName <> Entry.Name Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.RawName, 1)) + If Entry.Comp.TranslatedName <> Entry.Comp.RawName Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.TranslatedName, 1)) + If Entry.Comp.Description <> Entry.Description Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.Description, 0.4)) + End If + QueryList.Add(New SearchEntry(Of McMod) With {.Item = Entry, .SearchSource = SearchSource}) Next '进行搜索,构造列表 - Dim SearchResult = Search(QueryList, SearchBox.Text, MaxBlurCount:=5, MinBlurSimilarity:=0.35) + Dim SearchResult = Search(QueryList, SearchBox.Text, MaxBlurCount:=6, MinBlurSimilarity:=0.35) PanSearchList.Children.Clear() If SearchResult.Count = 0 Then PanSearch.Title = "无搜索结果" @@ -662,7 +534,7 @@ PanSearch.Title = "搜索结果" For Each Result In SearchResult Dim Item = McModListItem(Result.Item) - If ModeDebug Then Item.Info = If(Result.AbsoluteRight, "完全匹配,", "") & "相似度:" & Math.Round(Result.Similarity, 3) & "," & Item.Info + If ModeDebug Then Item.Description = If(Result.AbsoluteRight, "完全匹配,", "") & "相似度:" & Math.Round(Result.Similarity, 3) & "," & Item.Description PanSearchList.Children.Add(Item) Next PanSearchList.Visibility = Visibility.Visible @@ -692,4 +564,6 @@ End If End Sub +#End Region + End Class diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj index e902ab1..20b8ebf 100644 --- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj +++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj @@ -189,6 +189,9 @@ + + MyLocalModItem.xaml + MyExtraButton.xaml @@ -463,6 +466,10 @@ FormMain.xaml Code + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -905,5 +912,9 @@ + + + + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip index d1295a22dbe0aeac2ff39d3d137e24fcc190529c..5d452eaac638db227d2bab161d94b441f9dfbb51 100644 GIT binary patch delta 10705 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 delta 11279 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