龙腾猫跃 1 year ago
parent
commit
add4153067
52 changed files with 1650 additions and 1954 deletions
  1. 5 1
      Plain Craft Launcher 2/App.config
  2. 20 44
      Plain Craft Launcher 2/Application.xaml.vb
  3. 33 35
      Plain Craft Launcher 2/FormMain.xaml.vb
  4. 29 13
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  5. 1 1
      Plain Craft Launcher 2/Modules/Base/ModLoader.vb
  6. 411 776
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  7. 32 20
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  8. 2 2
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  9. 22 59
      Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
  10. 10 8
      Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
  11. 75 56
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  12. 108 86
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  13. 13 14
      Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
  14. 1 1
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  15. 1 4
      Plain Craft Launcher 2/Modules/ModSecret.vb
  16. 0 59
      Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb
  17. 22 23
      Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb
  18. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  19. 53 13
      Plain Craft Launcher 2/My Project/Resources.Designer.vb
  20. 19 7
      Plain Craft Launcher 2/My Project/Resources.resx
  21. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml
  22. 0 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb
  23. 10 15
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb
  24. 30 27
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  25. 1 2
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
  26. 10 9
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
  27. 11 9
      Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
  28. 6 5
      Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
  29. 5 3
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
  30. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml
  31. 1 0
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
  32. 4 2
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml
  33. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
  34. 491 468
      Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
  35. 1 1
      Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
  36. 3 3
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
  37. 1 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
  38. 6 14
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
  39. 4 4
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
  40. 11 5
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
  41. 25 33
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
  42. 2 2
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
  43. 45 6
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  44. BIN
      Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll
  45. BIN
      Plain Craft Launcher 2/Resources/CacheCow.Client.dll
  46. BIN
      Plain Craft Launcher 2/Resources/CacheCow.Common.dll
  47. 4 0
      Plain Craft Launcher 2/Resources/Custom.xaml
  48. BIN
      Plain Craft Launcher 2/Resources/Help.zip
  49. 117 117
      Plain Craft Launcher 2/Resources/ModData.txt
  50. BIN
      Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll
  51. BIN
      Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll
  52. BIN
      Plain Craft Launcher 2/Resources/java-wrapper.jar

+ 5 - 1
Plain Craft Launcher 2/App.config

@@ -30,6 +30,10 @@
                   <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
                   <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
             </dependentAssembly>
+			<dependentAssembly>
+				<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
+				<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="13.0.0.0"/>
+			</dependentAssembly>
         </assemblyBinding>
     </runtime>
-</configuration>
+</configuration>

+ 20 - 44
Plain Craft Launcher 2/Application.xaml.vb

@@ -181,53 +181,29 @@ WaitRetry:
     End Sub
 
     '动态 DLL 调用
-    Private Shared AssemblyNAudio As Assembly
-    Private Shared AssemblyJson As Assembly
-    Private Shared AssemblyDialog As Assembly
-    Private Shared AssemblyImazenWebp As Assembly
-    Private Shared ReadOnly AssemblyNAudioLock As New Object
-    Private Shared ReadOnly AssemblyJsonLock As New Object
-    Private Shared ReadOnly AssemblyDialogLock As New Object
-    Private Shared ReadOnly AssemblyImazenWebpLock As New Object
     Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean
-    Public Shared Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
-        If args.Name.StartsWithF("NAudio") Then
-            SyncLock AssemblyNAudioLock
-                If AssemblyNAudio Is Nothing Then
-                    Log("[Start] 加载 DLL:NAudio")
-                    AssemblyNAudio = Assembly.Load(GetResources("NAudio"))
-                End If
-                Return AssemblyNAudio
-            End SyncLock
-        ElseIf args.Name.StartsWithF("Newtonsoft.Json") Then
-            SyncLock AssemblyJsonLock
-                If AssemblyJson Is Nothing Then
-                    Log("[Start] 加载 DLL:Json")
-                    AssemblyJson = Assembly.Load(GetResources("Json"))
-                End If
-                Return AssemblyJson
-            End SyncLock
-        ElseIf args.Name.StartsWithF("Ookii.Dialogs.Wpf") Then
-            SyncLock AssemblyDialogLock
-                If AssemblyDialog Is Nothing Then
-                    Log("[Start] 加载 DLL:Dialogs")
-                    AssemblyDialog = Assembly.Load(GetResources("Dialogs"))
-                End If
-                Return AssemblyDialog
-            End SyncLock
-        ElseIf args.Name.StartsWithF("Imazen.WebP") Then
-            SyncLock AssemblyImazenWebpLock
-                If AssemblyImazenWebp Is Nothing Then
-                    Log("[Start] 加载 DLL:Imazen.WebP")
-                    AssemblyImazenWebp = Assembly.Load(GetResources("Imazen_WebP"))
-                    SetDllDirectory(PathPure.TrimEnd("\"))
+    Public Shared Function AssemblyResolve(sender As Object, Args As ResolveEventArgs) As Assembly
+        '缓存
+        Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "System.Net.Http.Formatting"}
+        Static Locks As New Dictionary(Of String, Object)(StringComparer.Ordinal)
+        Static LoadedAssembly As New Dictionary(Of String, Assembly)(StringComparer.Ordinal)
+        '查找对应的 DLL
+        Dim Prefix As String = Prefixes.FirstOrDefault(Function(p) Args.Name.StartsWithF(p))
+        If Prefix Is Nothing Then Return Nothing
+        '加载 DLL
+        If Not Locks.ContainsKey(Prefix) Then Locks(Prefix) = New Object()
+        SyncLock Locks(Prefix)
+            If Not LoadedAssembly.ContainsKey(Prefix) Then
+                Log($"[Start] 加载 DLL:{Prefix}")
+                LoadedAssembly(Prefix) = Assembly.Load(GetResources(Prefix))
+                'WebP 特判
+                If Prefix = "Imazen.WebP" Then
+                    SetDllDirectory(PathPure.TrimEnd("\"c))
                     WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64"))
                 End If
-                Return AssemblyImazenWebp
-            End SyncLock
-        Else
-            Return Nothing
-        End If
+            End If
+            Return LoadedAssembly(Prefix)
+        End SyncLock
     End Function
 
     '切换窗口

+ 33 - 35
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -83,6 +83,13 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '1:BUG-
+        If LastVersion < 363 Then 'Snapshot 2.10.4
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源时,会单独记忆每种资源上次下载到的文件夹,以防混淆"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:网络底层框架与下载稳定性"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法启动一部分 LabyMod 和 GTNH 客户端"))
+            FeatureCount += 22
+            BugCount += 26
+        End If
         If LastVersion < 362 Then 'Snapshot 2.10.3
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法安装部分使用老版本 PCL 导出的整合包"))
         End If
@@ -204,8 +211,6 @@ Public Class FormMain
         End Sub, "UpdateLog Output")
     End Sub
 
-    Private ReadOnly Helper As New DragFileHelper()
-
     '窗口加载
     Private IsWindowLoadFinished As Boolean = False
     Public Sub New()
@@ -228,7 +233,7 @@ Public Class FormMain
             If Not Setup.IsUnset("LaunchArgumentIndie") Then
                 Log("[Start] 从老 PCL 迁移版本隔离")
                 Setup.Set("LaunchArgumentIndieV2", Setup.Get("LaunchArgumentIndie"))
-            ElseIf Not Setup.IsUnset("WindowHeight") Then
+            ElseIf Not Setup.IsUnset("LaunchVersionSelect") Then
                 Log("[Start] 从老 PCL 升级,但此前未调整版本隔离,使用老的版本隔离默认值")
                 Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndie"))
             Else
@@ -244,36 +249,24 @@ Public Class FormMain
         [AddHandler](DragDrop.DragOverEvent, New DragEventHandler(AddressOf HandleDrag), handledEventsToo:=True)
         '加载 UI
         InitializeComponent()
-
-
-
         Opacity = 0
-        '旧代码
-        ''开启管理员权限下的文件拖拽,但下列代码也没用(#2531)
-        'If IsAdmin() Then
-        '    Log("[Start] PCL 正以管理员权限运行")
-        '    ChangeWindowMessageFilter(&H233, 1)
-        '    ChangeWindowMessageFilter(&H4A, 1)
-        '    ChangeWindowMessageFilter(&H49, 1)
-        'End If
-
         '开启管理员权限下的文件拖拽
         If IsAdmin() Then
-            AddHandler Me.SourceInitialized,
-            Sub(sender, e)
-                Dim wpfHelper As New WindowInteropHelper(Me)
-                Helper.HwndIntPtrSource = HwndSource.FromHwnd(wpfHelper.Handle)
+            Static Helper As New DragHelper
+            AddHandler SourceInitialized,
+            Sub()
+                Dim WpfHelper As New WindowInteropHelper(Me)
+                Helper.HwndIntPtrSource = HwndSource.FromHwnd(WpfHelper.Handle)
                 Helper.AddHook()
             End Sub
-            AddHandler Me.Closing,
-            Sub(sender, e)
-                Helper.RemoveDragHook()
-            End Sub
-            AddHandler Helper.DragDrop,
-            Sub(sender, e)
-                Me.FileDrag(Helper.DropFilePaths)
-            End Sub
+            AddHandler Closing, Sub() Helper.RemoveDragHook()
+            AddHandler Helper.DragDrop, Sub() FileDrag(Helper.DropFilePaths)
         End If
+        'If IsAdmin() Then '下列代码没用(#2531)
+        '    ChangeWindowMessageFilter(&H233, 1)
+        '    ChangeWindowMessageFilter(&H4A, 1)
+        '    ChangeWindowMessageFilter(&H49, 1)
+        'End If
         '切换到首页
         If Not IsNothing(FrmLaunchLeft.Parent) Then FrmLaunchLeft.SetValue(ContentPresenter.ContentProperty, Nothing)
         If Not IsNothing(FrmLaunchRight.Parent) Then FrmLaunchRight.SetValue(ContentPresenter.ContentProperty, Nothing)
@@ -390,7 +383,7 @@ Public Class FormMain
             Catch ex As Exception
                 Log(ex, "清理自动更新文件失败")
             End Try
-        End Sub, "Start Loader", ThreadPriority.Lowest)
+        End Sub, "初始化", ThreadPriority.Lowest)
 
         Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
     End Sub
@@ -616,11 +609,12 @@ Public Class FormMain
     '按键事件
     Private Sub FormMain_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
         If e.IsRepeat Then Return
-        '调用弹窗:回车选择第一个,Esc 选择最后一个
-        If PanMsg.Children.Count > 0 Then
+        '修复按下 Alt 后误认为弹出系统菜单导致的冻结
+        If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True
+        '在有弹窗时:回车选择第一个,Esc 选择最后一个
+        If PanMsg.Children.Any Then
             If e.Key = Key.Enter Then
                 CType(PanMsg.Children(0), Object).Btn1_Click()
-                Return
             ElseIf e.Key = Key.Escape Then
                 Dim Msg As Object = PanMsg.Children(0)
                 If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then
@@ -630,9 +624,14 @@ Public Class FormMain
                 Else
                     Msg.Btn1_Click()
                 End If
-                Return
             End If
+            Return
         End If
+
+        '==========================
+        ' 在没有弹窗时:继续检查……
+        '==========================
+
         '按 ESC 返回上一级
         If e.Key = Key.Escape Then TriggerPageBack()
         '更改隐藏版本可见性
@@ -666,8 +665,6 @@ Public Class FormMain
                 FrmLaunchLeft.LaunchButtonClick()
             End If
         End If
-        '修复按下 Alt 后误认为弹出系统菜单导致的冻结
-        If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True
     End Sub
     Private Sub FormMain_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseDown
         '鼠标侧键返回上一级
@@ -732,6 +729,7 @@ Public Class FormMain
     End Sub
     Private Sub FrmMain_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop
         Try
+            ShowWindowToTop()
             If e.Data.GetDataPresent(DataFormats.Text) Then
                 '获取文本
                 Try
@@ -1505,4 +1503,4 @@ Public Class FormMain
         lastMouseArg = e
     End Sub
 
-End Class
+End Class

+ 29 - 13
Plain Craft Launcher 2/Modules/Base/ModBase.vb

@@ -13,13 +13,13 @@ Public Module ModBase
 #Region "声明"
 
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.10.3" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.10.3." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.10.4" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.10.4." & VersionBranchCode '标准格式的四段式版本号
     Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
 #If BETA Then
     Public Const VersionCode As Integer = 361 'Release
 #Else
-    Public Const VersionCode As Integer = 362 'Snapshot
+    Public Const VersionCode As Integer = 363 'Snapshot
 #End If
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -1551,7 +1551,7 @@ RetryDir:
     ''' <summary>
     ''' 获取 JSON 对象。
     ''' </summary>
-    Public Function GetJson(Data As String)
+    Public Function GetJson(Data As String) As JToken
         Try
             Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local})
         Catch ex As Exception
@@ -2131,6 +2131,19 @@ RetryDir:
         End Function
     End Class
 
+    ''' <summary>
+    ''' 将二维数组转换为字典。
+    ''' </summary>
+    <Extension> Public Function ToDictionary(Of T)(arr As T(,)) As Dictionary(Of T, T)
+        Dim result As New Dictionary(Of T, T)
+        If arr.Length = 0 Then Return result
+        If arr.GetLength(1) <> 2 Then Throw New ArgumentException("数组必须为两列,第一列为 Key,第二列为 Value。")
+        For i As Integer = 0 To arr.GetLength(0) - 1
+            result(arr(i, 0)) = arr(i, 1)
+        Next
+        Return result
+    End Function
+
     ''' <summary>
     ''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。
     ''' </summary>
@@ -2179,10 +2192,10 @@ RetryDir:
     End Function
 
     ''' <summary>
-    ''' 判断当前系统语言是否为 zh-CN。
+    ''' 判断当前系统语言是否为中文。
     ''' </summary>
     Public Function IsSystemLanguageChinese() As Boolean
-        Return CultureInfo.CurrentCulture.Name = "zh-CN" OrElse CultureInfo.CurrentUICulture.Name = "zh-CN"
+        Return CultureInfo.CurrentCulture.Name.StartsWithF("zh-") OrElse CultureInfo.CurrentUICulture.Name.StartsWithF("zh-")
     End Function
 
     Private Uuid As Integer = 1
@@ -2225,6 +2238,9 @@ NextElement:
         Next i
         Return ResultArray
     End Function
+    <Extension> Public Function Any(Arr As UIElementCollection) As Boolean
+        Return Arr?.Count > 0
+    End Function
 
     ''' <summary>
     ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。
@@ -2462,7 +2478,7 @@ NextElement:
     ''' </summary>
     Public Sub RunInThread(Action As Action)
         If RunInUi() Then
-            RunInNewThread(Action, "Runtime Invoke " & GetUuid() & "#")
+            RunInNewThread(Action, "Invoke " & GetUuid())
         Else
             Action()
         End If
@@ -2760,11 +2776,11 @@ Retry:
             Using Reader As New XamlXmlReader(Stream)
                 While Reader.Read()
                     For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider)}
-                        If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 类型。")
-                        If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 值。")
+                        If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 类型。")
+                        If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 值。")
                     Next
                     For Each BlackListMember In {"Code", "FactoryMethod", "Static"}
-                        If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListMember} 成员。")
+                        If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListMember} 成员。")
                     Next
                 End While
             End Using
@@ -2922,7 +2938,7 @@ Retry:
         '处理错误会导致再次调用 Log() 导致无限循环
 
         '输出日志
-        Dim AppendText As String = "[" & GetTimeNow() & "] " & Text & vbCrLf '减轻同步锁占用
+        Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Text}{vbCrLf}" '减轻同步锁占用
         If ModeDebug Then
             SyncLock LogListLock
                 LogList.Append(AppendText)
@@ -2986,7 +3002,7 @@ Retry:
         Dim ExFull As String = Desc & ":" & GetExceptionDetail(Ex)
 
         '输出日志
-        Dim AppendText As String = "[" & GetTimeNow() & "] " & Desc & ":" & GetExceptionDetail(Ex, True) & vbCrLf '减轻同步锁占用
+        Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{GetExceptionDetail(Ex, True)}{vbCrLf}" '减轻同步锁占用
         If ModeDebug Then
             SyncLock LogListLock
                 LogList.Append(AppendText)
@@ -3048,7 +3064,7 @@ Retry:
         If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
             OpenExplorer(Path & "PCL\Log1.txt")
         End If
-        OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/")
+        OpenWebsite("https://github.com/Meloong-Git/PCL/issues/")
     End Sub
     Public Function CanFeedback(ShowHint As Boolean) As Boolean
         If False.Equals(PageSetupSystem.IsLauncherNewest) Then

+ 1 - 1
Plain Craft Launcher 2/Modules/Base/ModLoader.vb

@@ -339,7 +339,7 @@
                     [Error] = ex
                     State = LoadState.Failed
                 End Try
-            End Sub) With {.Name = Name, .Priority = ThreadPriority}
+            End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
             LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
         End Sub
         Public Overrides Sub Abort()

File diff suppressed because it is too large
+ 411 - 776
Plain Craft Launcher 2/Modules/Base/ModNet.vb


+ 32 - 20
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -1076,7 +1076,7 @@ Retry:
                     Try
                         '获取工程列表
                         Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
-                        Dim RequestResult As JObject = DlModRequest(CurseForgeUrl, IsJson:=True)
+                        Dim RequestResult As JObject = DlModRequest(CurseForgeUrl)
                         Task.Progress += 0.2
                         Dim ProjectList As New List(Of CompProject)
                         For Each JsonEntry As JObject In RequestResult("data")
@@ -1107,7 +1107,7 @@ Retry:
                 Sub()
                     Try
                         Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl)
-                        Dim RequestResult As JObject = DlModRequest(ModrinthUrl, IsJson:=True)
+                        Dim RequestResult As JObject = DlModRequest(ModrinthUrl)
                         Task.Progress += 0.2
                         Dim ProjectList As New List(Of CompProject)
                         For Each JsonEntry As JObject In RequestResult("hits")
@@ -1396,7 +1396,7 @@ Retry:
                     'DownloadAddress
                     Dim Url = Data("downloadUrl").ToString
                     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)) '对文件名进行编码
+                    Url = Url.Replace(FileName, WebUtility.UrlEncode(FileName)) '对文件名进行编码
                     DownloadUrls = HandleCurseForgeDownloadUrls(Url) '对脑残 CurseForge 的下载地址进行多种修正
                     DownloadUrls.AddRange(DownloadUrls.Select(Function(u) DlSourceModGet(u)).ToList) '添加镜像源,这个写法是为了让镜像源排在后面
                     DownloadUrls = DownloadUrls.Distinct.ToList '最终去重
@@ -1492,9 +1492,8 @@ Retry:
             Return {
                 Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."),
                 Url.Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."),
-                Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://media."),
                 Url.Replace("-service.overwolf.wtf", ".forgecdn.net"),
-                Url.Replace("://edge.", "://media."),
+                Url.Replace("://media.", "://edge."),
                 Url
             }.Distinct.ToList
         End Function
@@ -1591,27 +1590,40 @@ Retry:
         If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存
             TargetProject = CompProjectCache(ProjectId)
         ElseIf FromCurseForge Then 'CurseForge
-            TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True)("data"))
+            TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId)("data"))
         Else 'Modrinth
-            TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId, IsJson:=True))
+            TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId))
         End If
         '获取工程对象的文件列表
         If Not CompFilesCache.ContainsKey(ProjectId) Then '有缓存也不能直接返回,这时候前置可能没获取(#5173)
             Log("[Comp] 开始获取文件列表:" & ProjectId)
-            Dim ResultJsonArray As JArray
+            Dim ResultJsonArray As JArray = Nothing
             If FromCurseForge Then
                 'CurseForge
-                'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522)
-                ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=10000", IsJson:=True)("data")
-                '之前只请求一部分文件的方法备份如下:
-                'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件
-                '    ResultJsonArray = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json"))("data")
-                'Else '否则使用全部文件
-                '    ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999", IsJson:=True)("data")
-                'End If
+                For RetryCount As Integer = 0 To 2
+                    Dim ResultJson As JObject = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=" &
+                        (10000 + RetryCount)) '每次重试多请求一个文件,以避免触发 CDN 缓存
+                    'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522)
+                    '之前只请求一部分文件的方法备份如下:
+                    'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件
+                    '    ResultJsonArray = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json")("data")
+                    'Else '否则使用全部文件
+                    '    ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999")("data")
+                    'End If
+                    If ResultJson("pagination")("resultCount").ToObject(Of Integer) = ResultJson("pagination")("totalCount").ToObject(Of Integer) Then
+                        ResultJsonArray = ResultJson("data")
+                        Exit For
+                    ElseIf RetryCount < 2 Then
+                        Log($"[Comp] CurseForge 返回的文件列表存在缺失,即将进行第 {RetryCount + 1} 次重试", LogLevel.Debug) '#6224
+                        Log($"[Comp] 返回的原始内容如下:{vbCrLf}{ResultJson}")
+                    Else
+                        Log($"[Comp] CurseForge 返回的文件列表存在缺失,返回的原始内容如下:{vbCrLf}{ResultJson}")
+                        Throw New Exception("CurseForge 返回的文件列表存在缺失")
+                    End If
+                Next
             Else
                 'Modrinth
-                ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version", IsJson:=True)
+                ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version")
             End If
             CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
                 Where(Function(a) a.Available).ToList.
@@ -1625,10 +1637,10 @@ Retry:
             Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置:{Join(UndoneDeps, ",")}")
             Dim Projects As JArray
             If TargetProject.FromCurseForge Then
-                Projects = GetJson(DlModRequest("https://api.curseforge.com/v1/mods",
-                    "POST", "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json"))("data")
+                Projects = DlModRequest("https://api.curseforge.com/v1/mods",
+                    HttpMethod.Post, "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json")("data")
             Else
-                Projects = DlModRequest($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]", IsJson:=True)
+                Projects = DlModRequest($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]")
             End If
             For Each Project In Projects
                 Dim Unused As New CompProject(Project) '在 New 的时候会添加缓存以便之后读取

+ 2 - 2
Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb

@@ -1018,9 +1018,9 @@ NextStack:
                     End If
                 Case CrashReason.特定实体导致崩溃
                     If Additional.Count = 1 Then
-                        Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况:\n - 若正常运行,则是该实体导致出错,你或许需要使用一些方式删除此实体。\n - 若仍然出错,问题就可能来自其他原因……\h")
+                        Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况。\h")
                     Else
-                        Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,观察游戏的运行情况:\n - 若正常运行,则是某些实体导致出错,你或许需要删除该世界。\n - 若仍然出错,问题就可能来自其他原因……\h")
+                        Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,然后观察游戏的运行情况。\h")
                     End If
                 Case CrashReason.OptiFine与Forge不兼容
                     Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")

+ 22 - 59
Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb

@@ -198,7 +198,7 @@
     Private IsNewClientVersionHinted As Boolean = False
     Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult))
         Dim StartTime As Long = GetTimeTick()
-        Dim Json As JObject = NetGetCodeByRequestRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", IsJson:=True)
+        Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json"))
         Try
             Dim Versions As JArray = Json("versions")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
@@ -235,7 +235,7 @@
     ''' </summary>
     Public DlClientListBmclapiLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
     Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of String, DlClientListResult))
-        Dim Json As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", IsJson:=True)
+        Dim Json As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json"))
         Try
             Dim Versions As JArray = Json("versions")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
@@ -377,7 +377,7 @@
     ''' </summary>
     Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain)
     Private Sub DlOptiFineListOfficialMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
-        Dim Result As String = NetGetCodeByClient("https://optifine.net/downloads", Encoding.Default)
+        Dim Result As String = NetRequestByClientRetry("https://optifine.net/downloads", Encoding:=Encoding.Default)
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         Try
             '获取所有版本信息
@@ -413,7 +413,7 @@
     ''' </summary>
     Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain)
     Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
-        Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/optifine/versionList", IsJson:=True)
+        Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList"))
         Try
             Dim Versions As New List(Of DlOptiFineListEntry)
             For Each Token As JObject In Json
@@ -483,7 +483,7 @@
     ''' </summary>
     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("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html", UseBrowserUserAgent:=True)
+        Dim Result As String = NetRequestByClientRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding:=Encoding.Default, Accept:="text/html", UseBrowserUserAgent:=True)
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         '获取所有版本信息
         Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)")
@@ -497,7 +497,7 @@
     ''' </summary>
     Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain)
     Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
-        Dim Result As String = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding.Default)
+        Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding:=Encoding.Default)
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         '获取所有版本信息
         Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?")
@@ -629,7 +629,7 @@
     Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
         Dim Result As String
         Try
-            Result = NetGetCodeByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" &
+            Result = NetRequestByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" &
                                           Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057
                                           ".html", UseBrowserUserAgent:=True)
         Catch ex As Exception
@@ -700,9 +700,8 @@
     ''' Forge 版本列表,BMCLAPI。
     ''' </summary>
     Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
-        Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" &
-                                                      Loader.Input.Replace("-", "_"), '兼容 Forge 1.7.10-pre4,#4057
-                                                      IsJson:=True)
+        Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" &
+                                                      Loader.Input.Replace("-", "_"))) '兼容 Forge 1.7.10-pre4,#4057
         Dim Versions As New List(Of DlForgeVersionEntry)
         Try
             Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input)
@@ -836,8 +835,8 @@
     Public DlNeoForgeListOfficialLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Official", AddressOf DlNeoForgeListOfficialMain)
     Private Sub DlNeoForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
         '获取版本列表 JSON
-        Dim ResultLatest As String = NetGetCodeByLoader("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True)
-        Dim ResultLegacy As String = NetGetCodeByLoader("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True)
+        Dim ResultLatest As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge")
+        Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge")
         If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
         '解析
         Try
@@ -854,8 +853,8 @@
     Public DlNeoForgeListBmclapiLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Bmclapi", AddressOf DlNeoForgeListBmclapiMain)
     Public Sub DlNeoForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
         '获取版本列表 JSON
-        Dim ResultLatest As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True)
-        Dim ResultLegacy As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True)
+        Dim ResultLatest As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge")
+        Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge")
         If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
         '解析
         Try
@@ -960,7 +959,7 @@
     ''' </summary>
     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("https://dl.liteloader.com/versions/versions.json", IsJson:=True)
+        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json"))
         Try
             Dim Json As JObject = Result("versions")
             Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -988,7 +987,7 @@
     ''' </summary>
     Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain)
     Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
-        Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", IsJson:=True)
+        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json"))
         Try
             Dim Json As JObject = Result("versions")
             Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -1059,7 +1058,7 @@
     ''' </summary>
     Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain)
     Private Sub DlFabricListOfficialMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
-        Dim Result As JObject = NetGetCodeByRequestRetry("https://meta.fabricmc.net/v2/versions", IsJson:=True)
+        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions"))
         Try
             Dim Output = New DlFabricListResult With {.IsOfficial = True, .SourceName = "Fabric 官方源", .Value = Result}
             If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -1074,7 +1073,7 @@
     ''' </summary>
     Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain)
     Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
-        Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", IsJson:=True)
+        Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions"))
         Try
             Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result}
             If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -1101,50 +1100,14 @@
 #Region "DlMod | Mod 镜像源请求"
 
     ''' <summary>
-    ''' 对可能涉及 Mod 镜像源的请求进行处理,返回字符串或 JObject。
-    ''' 调用 NetGetCodeByRequest,会进行重试。
-    ''' </summary>
-    Public Function DlModRequest(Url As String, Optional IsJson As Boolean = False) As Object
-        Dim Urls As New List(Of KeyValuePair(Of String, Integer))
-        Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
-        Urls.Add(New KeyValuePair(Of String, Integer)(Url, 20))
-        'Dim McimUrl As String = DlSourceModGet(Url)
-        'If McimUrl <> Url Then
-        '    Select Case Setup.Get("ToolDownloadMod")
-        '        Case 0
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5))
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15))
-        '        Case 1
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5))
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15))
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
-        '        Case Else
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15))
-        '            Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
-        '    End Select
-        'End If
-        Dim Exs As String = ""
-        For Each Source In Urls
-            Try
-                Return NetGetCodeByRequestOnce(Source.Key, Encode:=Encoding.UTF8, Timeout:=Source.Value * 1000, IsJson:=IsJson, UseBrowserUserAgent:=True)
-            Catch ex As Exception
-                Exs += ex.Message + vbCrLf
-            End Try
-        Next
-        Throw New Exception(Exs)
-    End Function
-
-    ''' <summary>
-    ''' 对可能涉及 Mod 镜像源的请求进行处理。
+    ''' 对可能涉及 Mod 镜像源的请求进行处理,返回 JToken。
     ''' 调用 NetRequest,会进行重试。
     ''' </summary>
-    Public Function DlModRequest(Url As String, Method As String, Data As String, ContentType As String) As String
+    Public Function DlModRequest(Url As String, Optional Method As HttpMethod = Nothing,
+                                 Optional Content As String = Nothing, Optional ContentType As String = Nothing) As JToken
         Dim Urls As New List(Of KeyValuePair(Of String, Integer))
         Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
-        Urls.Add(New KeyValuePair(Of String, Integer)(Url, 20))
+        Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
         'Dim McimUrl As String = DlSourceModGet(Url)
         'If McimUrl <> Url Then
         '   Select Case Setup.Get("ToolDownloadMod")
@@ -1166,7 +1129,7 @@
         Dim Exs As String = ""
         For Each Source In Urls
             Try
-                Return NetRequestOnce(Source.Key, Method, Data, ContentType, Timeout:=Source.Value * 1000)
+                Return GetJson(NetRequestByClient(Source.Key, Method, Content, ContentType, Timeout:=Source.Value * 1000, Encoding:=Encoding.UTF8))
             Catch ex As Exception
                 Exs += ex.Message + vbCrLf
             End Try

+ 10 - 8
Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb

@@ -117,7 +117,7 @@
                 If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败")
                 If ModeDebug Then Log("[Java] Java 检查输出:" & PathFolder & "java.exe" & vbCrLf & Output)
                 If Output.Contains("/lib/ext exists") Then Throw New ApplicationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试")
-                If Output.Contains("a fatal error") Then Throw New ApplicationException("由于操作系统或 JVM 存在问题,无法运行该 Java。")
+                If Output.Contains("a fatal error") Then Throw New ApplicationException("无法运行该 Java,该 Java 或系统存在问题")
                 '获取详细信息
                 Dim VersionString = If(RegexSeek(Output, "(?<=version "")[^""]+"), If(RegexSeek(Output, "(?<=openjdk )[0-9]+"), "")).Replace("_", ".").Split("-").First
                 If VersionString.Split(".").Count > 4 Then VersionString = VersionString.Replace(".0.", ".") '#3493,VersionString = "21.0.2.0.2"
@@ -460,10 +460,12 @@ NoUserJava:
     ''' 将 Java 按照适用性排序。
     ''' </summary>
     Public Function JavaSorter(Left As JavaEntry, Right As JavaEntry) As Boolean
+        Dim PathInfo As New DirectoryInfo(ShortenPath(Path))
+        Dim PathMcInfo As New DirectoryInfo(ShortenPath(PathMcFolder))
         '1. 尽量在当前文件夹或当前 Minecraft 文件夹
         Dim ProgramPathParent As String, MinecraftPathParent As String = ""
-        ProgramPathParent = If(New DirectoryInfo(Path).Parent, New DirectoryInfo(Path)).FullName
-        If PathMcFolder <> "" Then MinecraftPathParent = If(New DirectoryInfo(PathMcFolder).Parent, New DirectoryInfo(PathMcFolder)).FullName
+        ProgramPathParent = If(PathInfo.Parent, PathInfo).FullName
+        If PathMcFolder <> "" Then MinecraftPathParent = If(PathMcInfo.Parent, PathMcInfo).FullName
         If Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Not Right.PathFolder.StartsWithF(ProgramPathParent) Then Return True
         If Not Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Right.PathFolder.StartsWithF(ProgramPathParent) Then Return False
         If PathMcFolder <> "" Then
@@ -530,7 +532,7 @@ NoUserJava:
                 If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705)
                 JavaSearchFolder(Disk.Name, JavaPreList, False)
             Next
-            '查找 ~/.jdks 文件夹中的 Java
+            '查找 .jdks 文件夹中的 Java
             JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\.jdks\", JavaPreList, False)
             '查找 APPDATA 文件夹中的 Java
             JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\", JavaPreList, False)
@@ -680,8 +682,8 @@ Wait:
             '若该目录有 Java,则加入结果
             If File.Exists(Path & "javaw.exe") Then Results(Path) = Source
             '查找其下的所有文件夹
-            '不应使用网易的 Java:https://github.com/HMeloong-Git/PCL/issues/1279#issuecomment-2761489121
-            Dim Keywords = {"java", "jdk", "env", "环境", "run", "软件", "jre", "mc", "dragon",
+            '不应使用网易的 Java:https://github.com/Meloong-Git/PCL/issues/1279#issuecomment-2761489121
+            Dim Keywords = {"java", "jdk", "env", "环境", "run", "软件", "jre", "mc", "dragon", "bin",
                             "soft", "cache", "temp", "corretto", "roaming", "users", "craft", "program", "世界", "net",
                             "游戏", "oracle", "game", "file", "data", "jvm", "服务", "server", "客户", "client", "整合",
                             "应用", "运行", "前置", "mojang", "官启", "新建文件夹", "eclipse", "microsoft", "hotspot",
@@ -748,7 +750,7 @@ Wait:
     Private LastJavaBaseDir As String = Nothing '用于在下载中断或失败时删除未完成下载的 Java 文件夹,防止残留只下了一半但 -version 能跑的 Java
     Private Sub JavaFileList(Loader As LoaderTask(Of Integer, List(Of NetFile)))
         Log("[Java] 开始获取 Java 下载信息")
-        Dim IndexFileStr As String = NetGetCodeByLoader(DlVersionListOrder(
+        Dim IndexFileStr As String = NetRequestByLoader(DlVersionListOrder(
             {"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"},
             {"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}
         ), IsJson:=True)
@@ -760,7 +762,7 @@ Wait:
         Dim Address As String = TargetEntry.Value("manifest")("url")
         Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}")
         '获取文件列表
-        Dim ListFileStr As String = NetGetCodeByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True)
+        Dim ListFileStr As String = NetRequestByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True)
         LastJavaBaseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Key & "\"
         Dim Results As New List(Of NetFile)
         For Each File As JProperty In CType(GetJson(ListFileStr), JObject)("files")

+ 75 - 56
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -684,11 +684,10 @@ LoginFinish:
         '发送登录请求
         Dim RequestData As New JObject(
             New JProperty("accessToken", AccessToken), New JProperty("clientToken", ClientToken), New JProperty("requestUser", True))
-        NetRequestRetry(
-            Url:=Data.Input.BaseUrl & "/validate",
-            Method:="POST",
-            Data:=RequestData.ToString(0),
-            Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
+        NetRequestByClientRetry(
+            Data.Input.BaseUrl & "/validate", HttpMethod.Post,
+            Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None),
+            Headers:={{"Accept-Language", "zh-CN"}},
             ContentType:="application/json; charset=utf-8") '没有返回值的
         '将登录结果输出
         Data.Output.AccessToken = AccessToken
@@ -701,10 +700,9 @@ LoginFinish:
     End Sub
     Private Sub McLoginRequestRefresh(ByRef Data As LoaderTask(Of McLoginServer, McLoginResult), RequestUser As Boolean)
         McLaunchLog("刷新登录开始(Refresh, " & Data.Input.Token & ")")
-        Dim LoginJson As JObject = GetJson(NetRequestRetry(
-               Url:=Data.Input.BaseUrl & "/refresh",
-               Method:="POST",
-               Data:=New JObject(
+        Dim LoginJson As JObject = GetJson(NetRequestByClientRetry(
+               Data.Input.BaseUrl & "/refresh", HttpMethod.Post,
+               Content:=New JObject(
                    New JProperty("selectedProfile", New JObject(
                        New JProperty("name", Setup.Get($"Cache{Data.Input.Token}Name")),
                        New JProperty("id", Setup.Get($"Cache{Data.Input.Token}Uuid"))
@@ -712,7 +710,7 @@ LoginFinish:
                    New JProperty("accessToken", Setup.Get($"Cache{Data.Input.Token}Access")),
                    New JProperty("requestUser", True)
                ).ToString(Newtonsoft.Json.Formatting.None),
-               Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
+               Headers:={{"Accept-Language", "zh-CN"}},
                ContentType:="application/json; charset=utf-8"))
         '将登录结果输出
         If LoginJson("selectedProfile") Is Nothing Then Throw New Exception("选择的角色 " & Setup.Get("Cache" & Data.Input.Token & "Name") & " 无效!")
@@ -739,11 +737,10 @@ LoginFinish:
                 New JProperty("username", Data.Input.UserName),
                 New JProperty("password", Data.Input.Password),
                 New JProperty("requestUser", True))
-            Dim LoginJson As JObject = GetJson(NetRequestRetry(
-                Url:=Data.Input.BaseUrl & "/authenticate",
-                Method:="POST",
-                Data:=RequestData.ToString(0),
-                Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
+            Dim LoginJson As JObject = GetJson(NetRequestByClientRetry(
+                Data.Input.BaseUrl & "/authenticate", HttpMethod.Post,
+                Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None),
+                Headers:={{"Accept-Language", "zh-CN"}},
                 ContentType:="application/json; charset=utf-8"))
             '检查登录结果
             If LoginJson("availableProfiles").Count = 0 Then
@@ -858,8 +855,9 @@ LoginFinish:
         '初始请求
 Retry:
         McLaunchLog("开始微软登录步骤 1/6(原始登录)")
-        Dim PrepareJson As JObject = GetJson(NetRequestRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", "POST",
-            $"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access", "application/x-www-form-urlencoded"))
+        Dim PrepareJson As JObject = GetJson(NetRequestByClientRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", HttpMethod.Post,
+            Content:=$"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access",
+            Accept:="application/x-www-form-urlencoded"))
         McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString)
 
         '弹窗
@@ -888,9 +886,11 @@ Retry:
 
         Dim Result As String
         Try
-            Result = NetRequestMultiple("https://login.live.com/oauth20_token.srf", "POST",
-                $"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access",
-                "application/x-www-form-urlencoded", 2)
+            Result = NetRequestByClientMultiple("https://login.live.com/oauth20_token.srf", HttpMethod.Post,
+                Content:=$"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access",
+                ContentType:="application/x-www-form-urlencoded",
+                Headers:={{"Accept-Language", "en-US,en;q=0.5"}, {"X-Requested-With", "XMLHttpRequest"}},
+                ThreadCount:=2)
         Catch ex As Exception
             If ex.Message.ContainsF("must sign in again", True) OrElse ex.Message.ContainsF("password expired", True) OrElse
                (ex.Message.Contains("refresh_token") AndAlso ex.Message.Contains("is not valid")) Then '#269
@@ -918,8 +918,8 @@ Retry:
            ""RelyingParty"": ""http://auth.xboxlive.com"",
            ""TokenType"": ""JWT""
         }"
-        Dim Result As String = NetRequestMultiple("https://user.auth.xboxlive.com/user/authenticate", "POST", Request, "application/json", 3)
-
+        Dim Result As String = NetRequestByClientMultiple("https://user.auth.xboxlive.com/user/authenticate", HttpMethod.Post,
+            Content:=Request, ContentType:="application/json")
         Dim ResultJson As JObject = GetJson(Result)
         Dim XBLToken As String = ResultJson("Token").ToString
         Return XBLToken
@@ -940,7 +940,8 @@ Retry:
                                  }"
         Dim Result As String
         Try
-            Result = NetRequestMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", "POST", Request, "application/json", 3)
+            Result = NetRequestByClientMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post,
+                Content:=Request, ContentType:="application/json")
         Catch ex As Net.WebException
             '参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js
             If ex.Message.Contains("2148916227") Then
@@ -971,10 +972,15 @@ Retry:
             End If
         End Try
 
-        Dim ResultJson As JObject = GetJson(Result)
-        Dim XSTSToken As String = ResultJson("Token").ToString
-        Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString
-        Return {XSTSToken, UHS}
+        Try '这个 Try 块仅为了追踪 #6683 而添加
+            Dim ResultJson As JObject = GetJson(Result)
+            Dim XSTSToken As String = ResultJson("Token").ToString
+            Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString
+            Return {XSTSToken, UHS}
+        Catch
+            Log("[Launch] 返回内容:" & vbCrLf & Result)
+            Throw
+        End Try
     End Function
     '微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken
     Private Function MsLoginStep4(Tokens As String()) As String
@@ -983,7 +989,8 @@ Retry:
         Dim Request As String = New JObject(New JProperty("identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}")).ToString(0)
         Dim Result As String
         Try
-            Result = NetRequestRetry("https://api.minecraftservices.com/authentication/login_with_xbox", "POST", Request, "application/json")
+            Result = NetRequestByClientRetry("https://api.minecraftservices.com/authentication/login_with_xbox", HttpMethod.Post,
+                Content:=Request, ContentType:="application/json")
         Catch ex As Net.WebException
             Dim Message As String = GetExceptionSummary(ex)
             If Message.Contains("(429)") Then
@@ -1005,7 +1012,8 @@ Retry:
     Private Sub MsLoginStep5(AccessToken As String)
         McLaunchLog("开始微软登录步骤 5/6")
 
-        Dim Result As String = NetRequestMultiple("https://api.minecraftservices.com/entitlements/mcstore", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
+        Dim Result As String = NetRequestByClientMultiple("https://api.minecraftservices.com/entitlements/mcstore",
+            ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}})
         Try
             Dim ResultJson As JObject = GetJson(Result)
             If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then
@@ -1026,7 +1034,8 @@ Retry:
 
         Dim Result As String
         Try
-            Result = NetRequestMultiple("https://api.minecraftservices.com/minecraft/profile", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
+            Result = NetRequestByClientMultiple("https://api.minecraftservices.com/minecraft/profile",
+                ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}})
         Catch ex As Net.WebException
             Dim Message As String = GetExceptionSummary(ex)
             If Message.Contains("(429)") Then
@@ -1099,7 +1108,7 @@ Retry:
         If Len(Uuid) = 32 Then Return Uuid
         '从官网获取
         Try
-            Dim GotJson As JObject = NetGetCodeByRequestRetry("https://api.mojang.com/users/profiles/minecraft/" & Name, IsJson:=True)
+            Dim GotJson As JObject = GetJson(NetRequestByClientRetry("https://api.mojang.com/users/profiles/minecraft/" & Name))
             If GotJson Is Nothing Then Throw New FileNotFoundException("正版玩家档案不存在(" & Name & ")")
             Uuid = If(GotJson("id"), "")
         Catch ex As Exception
@@ -1205,6 +1214,12 @@ Retry:
             End If
         End If
 
+        'LiteLoader 检测
+        If McVersionCurrent.Version.HasLiteLoader AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本
+            '最高 Java 8
+            MaxVer = If(New Version(1, 8, 999, 999) < MaxVer, New Version(1, 8, 999, 999), MaxVer)
+        End If
+
         '统一通行证检测
         If Setup.Get("LoginType") = McLoginType.Nide Then
             '至少 Java 8u101
@@ -1356,16 +1371,6 @@ Retry:
         If McLaunchJavaSelected.VersionCode >= 18 Then
             If Not Arguments.Contains("-Dfile.encoding=") Then Arguments = "-Dfile.encoding=COMPAT " & Arguments
         End If
-        '替换参数
-        Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader)
-        If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
-            '若自定义信息为空,则去掉该部分
-            Arguments = Arguments.Replace(" --versionType ${version_type}", "")
-            ReplaceArguments("${version_type}") = """"""
-        End If
-        For Each entry As KeyValuePair(Of String, String) In ReplaceArguments
-            Arguments = Arguments.Replace(entry.Key, If(entry.Value.Contains(" ") OrElse entry.Value.Contains(":\"), """" & entry.Value & """", entry.Value))
-        Next
         'MJSB
         Arguments = Arguments.Replace(" -Dos.name=Windows 10", " -Dos.name=""Windows 10""")
         '全屏
@@ -1374,31 +1379,47 @@ Retry:
         For Each Arg In CurrentLaunchOptions.ExtraArgs
             Arguments += " " & Arg.Trim
         Next
+        '自定义
+        Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent)
+        Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame)
+        '进行参数替换
+        Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader)
+        If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
+            '若自定义信息为空,则去掉该部分
+            Arguments = Arguments.Replace(" --versionType ${version_type}", "")
+            ReplaceArguments("${version_type}") = """"""
+        End If
+        Dim FinalArguments As String = ""
+        For Each Argument In Arguments.Split(" ")
+            For Each Entry As KeyValuePair(Of String, String) In ReplaceArguments
+                Argument = Argument.Replace(Entry.Key, Entry.Value)
+            Next
+            If (Argument.Contains(" ") OrElse Argument.Contains(":\")) AndAlso Not Argument.EndsWithF("""") Then Argument = $"""{Argument}"""
+            FinalArguments += Argument & " "
+        Next
+        FinalArguments = FinalArguments.TrimEnd
         '进服
         Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp)
         If Server.Length > 0 Then
             If McVersionCurrent.ReleaseTime > New Date(2023, 4, 4) Then
                 'QuickPlay
-                Arguments += $" --quickPlayMultiplayer ""{Server}"""
+                FinalArguments += $" --quickPlayMultiplayer ""{Server}"""
             Else
                 '老版本
                 If Server.Contains(":") Then
                     '包含端口号
-                    Arguments += " --server " & Server.Split(":")(0) & " --port " & Server.Split(":")(1)
+                    FinalArguments += $" --server {Server.Split(":")(0)} --port {Server.Split(":")(1)}"
                 Else
                     '不包含端口号
-                    Arguments += " --server " & Server & " --port 25565"
+                    FinalArguments += $" --server {Server} --port 25565"
                 End If
                 If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Critical)
             End If
         End If
-        '自定义
-        Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent)
-        Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame)
         '输出
         McLaunchLog("Minecraft 启动参数:")
-        McLaunchLog(Arguments)
-        McLaunchArgument = Arguments
+        McLaunchLog(FinalArguments)
+        McLaunchArgument = FinalArguments
     End Sub
 
     'Jvm 部分(第一段)
@@ -1424,12 +1445,12 @@ Retry:
         End If
         'Authlib-Injector
         If McLoginLoader.Output.Type = "Auth" Then
-            If (McLaunchJavaSelected.VersionCode >= 6) Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT")
+            If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#)
             Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy,
                 "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录
                 Setup.Get("VersionServerAuthServer", McVersionCurrent))
             Try
-                Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
+                Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8)
                 DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
@@ -1494,12 +1515,12 @@ NextVersion:
         End If
         'Authlib-Injector
         If McLoginLoader.Output.Type = "Auth" Then
-            If (McLaunchJavaSelected.VersionCode >= 6) Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT")
+            If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#)
             Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy,
                 "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录
                 Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
             Try
-                Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
+                Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8)
                 DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
@@ -1533,10 +1554,8 @@ NextVersion:
             DeDuplicateDataList.Add(CurrentEntry.Trim.Replace("McEmu= ", "McEmu="))
         Next
 
-        '#3511 的清理
-        DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M")
-
         '去重
+        DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") '#3511 的清理
         Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ")
 
         '添加 MainClass

+ 108 - 86
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -169,8 +169,8 @@ Public Module ModMinecraft
             _McVersionCurrent = value '由于有可能是 Nothing,导致无法初始化,才得这样弄一圈
             _McVersionLast = value
             If value Is Nothing Then Return
-            '重置缓存的 Mod 文件夹
-            PageDownloadCompDetail.CachedFolder = Nothing
+            '重置缓存的下载文件夹
+            PageDownloadCompDetail.CachedFolder.Clear()
             '统一通行证重判
             If AniControlEnabled = 0 AndAlso
                Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso
@@ -440,6 +440,33 @@ VersionSearchFinish:
         ''' </summary>
         Public ReleaseTime As New Date(1970, 1, 1, 15, 0, 0)
 
+        ''' <summary>
+        ''' 获取该版本的 JSON 路径。
+        ''' </summary>
+        Public Function GetJsonPath() As String
+            Dim JsonPath As String = Path & Name & ".json"
+            If Not File.Exists(JsonPath) Then
+                '尝试寻找 JSON 文件
+                JsonPath = Nothing
+                For Each JsonCandidatePath In Directory.GetFiles(Path, "*.json")
+                    Try
+                        Dim JsonCandidate As JObject = GetJson(ReadFile(JsonCandidatePath))
+                        If Not JsonCandidate.ContainsKey("mainClass") Then Continue For
+                        If Not JsonCandidate.ContainsKey("type") Then Continue For
+                        If Not JsonCandidate.ContainsKey("id") Then Continue For
+                    Catch
+                    End Try
+                    JsonPath = JsonCandidatePath
+                    Exit For
+                Next
+                If JsonPath Is Nothing Then
+                    Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json")
+                Else
+                    Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug)
+                End If
+            End If
+            Return JsonPath
+        End Function
         ''' <summary>
         ''' 该版本的 Json 文本。
         ''' </summary>
@@ -452,17 +479,7 @@ VersionSearchFinish:
                     Return TrimedJson.StartsWithF("{") AndAlso TrimedJson.EndsWithF("}")
                 End Function
                 If _JsonText Is Nothing Then
-                    Dim JsonPath As String = Path & Name & ".json"
-                    If Not File.Exists(JsonPath) Then
-                        '如果文件夹下只有一个 JSON 文件,则将其作为版本 JSON
-                        Dim JsonFiles As String() = Directory.GetFiles(Path, "*.json")
-                        If JsonFiles.Count = 1 Then
-                            JsonPath = JsonFiles(0)
-                            Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug)
-                        Else
-                            Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json")
-                        End If
-                    End If
+                    Dim JsonPath As String = GetJsonPath()
                     _JsonText = ReadFile(JsonPath)
                     '如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试
                     If Not FastJsonCheck(_JsonText) Then
@@ -540,9 +557,15 @@ Recheck:
                                 '继续循环
                                 If Inherit.InheritVersion = InheritVersion Then Throw New Exception("版本依赖项出现嵌套:" & InheritVersion)
                                 InheritVersion = Inherit.InheritVersion
-                                '合并
+                                '合并 Libraries 项:子版本放在前面,父版本放在后面(5978#:如果多个 jar 包中含有相同的类,Java 8 和之前的版本按照 -cp 指定的顺序选择第一个)
+                                Dim CurrentLib As JArray = _JsonObject("libraries").DeepClone()
+                                For Each LibToken In Inherit.JsonObject("libraries")
+                                    CurrentLib.Add(LibToken)
+                                Next
+                                '合并其他项:子版本优先于父版本
                                 Inherit.JsonObject.Merge(_JsonObject)
                                 _JsonObject = Inherit.JsonObject
+                                _JsonObject("libraries") = CurrentLib
                                 GoTo Recheck
                             End If
                         Catch ex As Exception
@@ -580,20 +603,18 @@ Recheck:
             Get
                 If Not JsonVersionInited Then
                     JsonVersionInited = True
-                    If File.Exists(Path & Name & ".jar") Then
-                        Try
-                            Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
-                                Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json")
-                                If VersionJson IsNot Nothing Then
-                                    Using VersionJsonStream As New StreamReader(VersionJson.Open)
-                                        _JsonVersion = GetJson(VersionJsonStream.ReadToEnd)
-                                    End Using
-                                End If
+                    Try
+                        If Not File.Exists($"{Path}{Name}.jar") Then Exit Try
+                        Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+                            Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json")
+                            If VersionJson Is Nothing Then Exit Try
+                            Using VersionJsonStream As New StreamReader(VersionJson.Open)
+                                _JsonVersion = GetJson(VersionJsonStream.ReadToEnd)
                             End Using
-                        Catch ex As Exception
-                            Log(ex, "从版本 jar 中读取 version.json 失败")
-                        End Try
-                    End If
+                        End Using
+                    Catch ex As Exception
+                        Log(ex, $"从版本 jar 中读取 version.json 失败({Path}{Name}.jar)")
+                    End Try
                 End If
                 Return _JsonVersion
             End Get
@@ -1538,7 +1559,7 @@ OnLoaded:
     ''' 要求玩家选择一个皮肤文件,并进行相关校验。
     ''' </summary>
     Public Function McSkinSelect() As McSkinInfo
-        Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.webp)|*.png;*.jpg;*.webp", "选择皮肤文件")
+        Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.jpeg;*.webp)|*.png;*.jpg;*.jpeg;*.webp", "选择皮肤文件")
 
         '验证有效性
         If FileName = "" Then Return New McSkinInfo With {.IsVaild = False}
@@ -1589,7 +1610,7 @@ OnLoaded:
             Case Else
                 Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null"))
         End Select
-        Dim SkinString = NetGetCodeByRequestRetry(Url & Uuid)
+        Dim SkinString = NetRequestByClientRetry(Url & Uuid)
         If SkinString = "" Then Throw New Exception("皮肤返回值为空,可能是未设置自定义皮肤的用户")
         '处理皮肤地址
         Dim SkinValue As String
@@ -1611,7 +1632,7 @@ OnLoaded:
             Throw New Exception("用户未设置自定义皮肤")
         Else
             Dim SkinUrl As String = SkinJson("textures")("skin")("url").ToString
-            SkinValue = If(SkinUrl.Contains("minecraft.net"),SkinUrl.Replace("http:", "https:"),SkinUrl)
+            SkinValue = If(SkinUrl.Contains("minecraft.net/"), SkinUrl.Replace("http://", "https://"), SkinUrl)
         End If
         '保存缓存
         WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid, SkinValue)
@@ -1628,9 +1649,9 @@ OnLoaded:
         Dim FileAddress As String = PathTemp & "Cache\Skin\" & GetHash(Address) & ".png"
         SyncLock McSkinDownloadLock
             If Not File.Exists(FileAddress) Then
-                NetDownloadByClient(Address, FileAddress & NetDownloadEnd)
+                NetDownloadByClient(Address, FileAddress & ".PCLDownloading")
                 File.Delete(FileAddress)
-                FileSystem.Rename(FileAddress & NetDownloadEnd, FileAddress)
+                FileSystem.Rename(FileAddress & ".PCLDownloading", FileAddress)
                 Log("[Minecraft] 皮肤下载成功:" & FileAddress)
             End If
             Return FileAddress
@@ -1773,61 +1794,60 @@ OnLoaded:
 
         '获取当前支持库列表
         Log("[Minecraft] 获取支持库列表:" & Version.Name)
-        McLibListGet = McLibListGetWithJson(Version.JsonObject)
-        If Not IncludeVersionJar Then Return McLibListGet
+        Dim Result = McLibListGetWithJson(Version.JsonObject)
 
         '需要添加原版 Jar
-        Dim RealVersion As McVersion
-        Dim RequiredJar As String = Version.JsonObject("jar")?.ToString
-        If Version.IsHmclFormatJson OrElse RequiredJar Is Nothing Then
-            'HMCL 项直接使用自身的 Jar
-            '根据 Inherit 获取最深层版本
-            Dim OriginalVersion As McVersion = Version
-            '1.17+ 的 Forge 不寻找 Inherit
-            If Not ((Version.Version.HasForge OrElse Version.Version.HasNeoForge) AndAlso Version.Version.McCodeMain >= 17) Then
-                Do Until OriginalVersion.InheritVersion = ""
-                    If OriginalVersion.InheritVersion = OriginalVersion.Name Then Exit Do
-                    OriginalVersion = New McVersion(PathMcFolder & "versions\" & OriginalVersion.InheritVersion & "\")
-                Loop
+        If IncludeVersionJar Then
+            Dim RealVersion As McVersion
+            Dim RequiredJar As String = Version.JsonObject("jar")?.ToString
+            If Version.IsHmclFormatJson OrElse RequiredJar Is Nothing Then
+                'HMCL 项直接使用自身的 Jar
+                '根据 Inherit 获取最深层版本
+                Dim OriginalVersion As McVersion = Version
+                '1.17+ 的 Forge 不寻找 Inherit
+                If Not ((Version.Version.HasForge OrElse Version.Version.HasNeoForge) AndAlso Version.Version.McCodeMain >= 17) Then
+                    Do Until OriginalVersion.InheritVersion = ""
+                        If OriginalVersion.InheritVersion = OriginalVersion.Name Then Exit Do
+                        OriginalVersion = New McVersion(PathMcFolder & "versions\" & OriginalVersion.InheritVersion & "\")
+                    Loop
+                End If
+                '需要新建对象,否则后面的 Check 会导致 McVersionCurrent 的 State 变回 Original
+                '复现:启动一个 Snapshot 版本
+                RealVersion = New McVersion(OriginalVersion.Path)
+            Else
+                'Json 已提供 Jar 字段,使用该字段的信息
+                RealVersion = New McVersion(RequiredJar)
             End If
-            '需要新建对象,否则后面的 Check 会导致 McVersionCurrent 的 State 变回 Original
-            '复现:启动一个 Snapshot 版本
-            RealVersion = New McVersion(OriginalVersion.Path)
-        Else
-            'Json 已提供 Jar 字段,使用该字段的信息
-            RealVersion = New McVersion(RequiredJar)
-        End If
-        Dim ClientUrl As String, ClientSHA1 As String
-        '判断需求的版本是否存在
-        '不能调用 RealVersion.Check(),可能会莫名其妙地触发 CheckPermission 正被另一进程使用,导致误判前置不存在
-        If Not File.Exists(RealVersion.Path & RealVersion.Name & ".json") Then
-            RealVersion = Version
-            Log("[Minecraft] 可能缺少前置版本 " & RealVersion.Name & ",找不到对应的 json 文件", LogLevel.Debug)
-        End If
-        '获取详细下载信息
-        If RealVersion.JsonObject("downloads") IsNot Nothing AndAlso RealVersion.JsonObject("downloads")("client") IsNot Nothing Then
-            ClientUrl = RealVersion.JsonObject("downloads")("client")("url")
-            ClientSHA1 = RealVersion.JsonObject("downloads")("client")("sha1")
-        Else
-            ClientUrl = Nothing
-            ClientSHA1 = Nothing
+            Dim ClientUrl As String, ClientSHA1 As String
+            '判断需求的版本是否存在
+            '不能调用 RealVersion.Check(),可能会莫名其妙地触发 CheckPermission 正被另一进程使用,导致误判前置不存在
+            If Not File.Exists(RealVersion.Path & RealVersion.Name & ".json") Then
+                RealVersion = Version
+                Log("[Minecraft] 可能缺少前置版本 " & RealVersion.Name & ",找不到对应的 json 文件", LogLevel.Debug)
+            End If
+            '获取详细下载信息
+            If RealVersion.JsonObject("downloads") IsNot Nothing AndAlso RealVersion.JsonObject("downloads")("client") IsNot Nothing Then
+                ClientUrl = RealVersion.JsonObject("downloads")("client")("url")
+                ClientSHA1 = RealVersion.JsonObject("downloads")("client")("sha1")
+            Else
+                ClientUrl = Nothing
+                ClientSHA1 = Nothing
+            End If
+            '把所需的原版 Jar 添加进去
+            Result.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1})
         End If
-        '把所需的原版 Jar 添加进去
-        McLibListGet.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1})
 
+        Return Result
     End Function
     ''' <summary>
     ''' 获取 Minecraft 某一版本忽视继承的支持库列表,即结果中没有继承项。
     ''' </summary>
     Public Function McLibListGetWithJson(JsonObject As JObject, Optional KeepSameNameDifferentVersionResult As Boolean = False, Optional CustomMcFolder As String = Nothing) As List(Of McLibToken)
         CustomMcFolder = If(CustomMcFolder, PathMcFolder)
-        Dim BasicArray As New List(Of McLibToken)
-
-        '添加基础 Json 项
-        Dim AllLibs As JArray = JsonObject("libraries")
 
         '转换为 LibToken
-        For Each Library As JObject In AllLibs.Children
+        Dim BasicArray As New List(Of McLibToken)
+        For Each Library As JObject In CType(JsonObject("libraries"), JArray).Children
 
             '清理 null 项(BakaXL 会把没有的项序列化为 null,但会被 Newtonsoft 转换为 JValue,导致 Is Nothing = false;这导致了 #409)
             For i = Library.Properties.Count - 1 To 0 Step -1
@@ -1946,8 +1966,7 @@ OnLoaded:
             Try
                 Log("[Minecraft] 开始获取统一通行证下载信息")
                 '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/
-                DownloadInfo = GetJson(NetGetCodeByLoader({
-                        "https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)}, IsJson:=True))
+                DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)))
             Catch ex As Exception
                 Log(ex, "获取统一通行证下载信息失败")
             End Try
@@ -1969,10 +1988,9 @@ OnLoaded:
             '获取下载信息
             Try
                 Log("[Minecraft] 开始获取 Authlib-Injector 下载信息")
-                DownloadInfo = GetJson(NetGetCodeByLoader({
+                DownloadInfo = GetJson(NetRequestByClientRetry(
                         "https://authlib-injector.yushi.moe/artifact/latest.json",
-                        "https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"
-                    }, IsJson:=True))
+                        BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"))
             Catch ex As Exception
                 Log(ex, "获取 Authlib-Injector 下载信息失败")
             End Try
@@ -2017,12 +2035,16 @@ OnLoaded:
         '获取
         For Each Token As McLibToken In Libs
             '检查文件
-            Dim Checker As New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1)
+            Dim Checker As FileChecker
+            If Token.Name.ContainsF("labymod") Then
+                Checker = New FileChecker '不检查 LabyMod 的文件,它们提供的文件校验信息是错的(#3225)
+            Else
+                Checker = New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1)
+            End If
             If Checker.Check(Token.LocalPath) Is Nothing Then Continue For
             '文件不符合,添加下载
             Dim Urls As New List(Of String)
-            If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then
-                '特判修复 Forge 部分 universal 文件缺失 URL(#5455)
+            If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then '特判修复 Forge 部分 universal 文件缺失 URL(#5455)
                 Token.Url = "https://maven.minecraftforge.net" & Token.LocalPath.Replace(CustomMcFolder & "libraries", "").Replace("\", "/")
             End If
             If Token.Url IsNot Nothing Then
@@ -2194,16 +2216,16 @@ OnLoaded:
                 Dim LocalPath As String
                 If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToObject(Of Boolean) Then
                     'Remap
-                    LocalPath = Version.PathIndie & "resources\" & File.Name.Replace("/", "\")
+                    LocalPath = $"{Version.PathIndie}resources\{File.Name}"
                 ElseIf Json("virtual") IsNot Nothing AndAlso Json("virtual").ToObject(Of Boolean) Then
                     'Virtual
-                    LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\")
+                    LocalPath = $"{PathMcFolder}assets\virtual\legacy\{File.Name}"
                 Else
                     '正常
-                    LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString
+                    LocalPath = $"{PathMcFolder}assets\objects\{Left(File.Value("hash").ToString, 2)}\{File.Value("hash")}"
                 End If
                 Result.Add(New McAssetsToken With {
-                    .LocalPath = LocalPath,
+                    .LocalPath = LocalPath.Replace("/", "\"),
                     .SourcePath = File.Name,
                     .Hash = File.Value("hash").ToString,
                     .Size = File.Value("size").ToString

+ 13 - 14
Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb

@@ -1,7 +1,7 @@
 Imports System.IO.Compression
 
 Public Module ModMod
-    Private Const LocalModCacheVersion As Integer = 7
+    Private Const LocalModCacheVersion As Integer = 8
 
     Public Class McMod
 
@@ -946,8 +946,8 @@ Finished:
             Try
                 '步骤 1:获取 Hash 与对应的工程 ID
                 Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList()
-                Dim ModrinthVersion = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files", "POST",
-                    $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject)
+                Dim ModrinthVersion As JObject = DlModRequest("https://api.modrinth.com/v2/version_files", HttpMethod.Post,
+                    $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")
                 Log($"[Mod] 从 Modrinth 获取到 {ModrinthVersion.Count} 个本地 Mod 的对应信息")
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
                 If ModrinthVersion.Count = 0 Then Return
@@ -967,9 +967,8 @@ Finished:
                 Log($"[Mod] 需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息")
                 '步骤 3:获取工程信息
                 If Not ModrinthMapping.Any() Then Return
-                Dim ModrinthProject = CType(GetJson(DlModRequest(
-                    $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]",
-                    "GET", "", "application/json")), JArray)
+                Dim ModrinthProject As JArray = DlModRequest(
+                    $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]")
                 For Each ProjectJson In ModrinthProject
                     Dim Project As New CompProject(ProjectJson)
                     For Each Entry In ModrinthMapping(Project.Id)
@@ -978,9 +977,9 @@ Finished:
                 Next
                 Log($"[Mod] 已从 Modrinth 获取本地 Mod 信息,继续获取更新信息")
                 '步骤 4:获取更新信息
-                Dim ModrinthUpdate = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files/update", "POST",
+                Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update", HttpMethod.Post,
                     $"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"", 
-                    ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json")), JObject)
+                    ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json")
                 For Each Entry In Mods
                     If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.CompFile Is Nothing Then Continue For
                     Dim UpdateFile As New CompFile(ModrinthUpdate(Entry.ModrinthHash), CompType.Mod)
@@ -1015,8 +1014,8 @@ Finished:
                     CurseForgeHashes.Add(Entry.CurseForgeHash)
                     If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return
                 Next
-                Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432", "POST",
-                    $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
+                Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432", HttpMethod.Post,
+                    $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches")
                 Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息")
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
                 If Not CurseForgeRaw.Any() Then Return
@@ -1038,8 +1037,8 @@ Finished:
                 Log($"[Mod] 需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息")
                 '步骤 3:获取工程信息
                 If Not CurseForgeMapping.Any() Then Return
-                Dim CurseForgeProject = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods", "POST",
-                    $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data")
+                Dim CurseForgeProject = DlModRequest("https://api.curseforge.com/v1/mods", HttpMethod.Post,
+                    $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")("data")
                 Dim UpdateFileIds As New Dictionary(Of Integer, List(Of McMod)) 'FileId -> 本地 Mod 文件列表
                 Dim FileIdToProjectSlug As New Dictionary(Of Integer, String)
                 For Each ProjectJson In CurseForgeProject
@@ -1079,8 +1078,8 @@ Finished:
                 Log($"[Mod] 已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息")
                 '步骤 4:获取更新文件信息
                 If Not UpdateFileIds.Any() Then Return
-                Dim CurseForgeFiles = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST",
-                                    $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")), JObject)("data")
+                Dim CurseForgeFiles = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post,
+                                    $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")("data")
                 Dim UpdateFiles As New Dictionary(Of McMod, CompFile)
                 For Each FileJson In CurseForgeFiles
                     Dim File As New CompFile(FileJson, CompType.Mod)

+ 1 - 1
Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb

@@ -244,7 +244,7 @@ Retry:
             '获取 Mod 下载信息
             ModDownloadLoaders.Add(New LoaderTask(Of Integer, JArray)("获取 Mod 下载信息",
             Sub(Task As LoaderTask(Of Integer, JArray))
-                Task.Output = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json"))("data")
+                Task.Output = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json")("data")
                 '如果文件已被删除,则 API 会跳过那一项
                 If ModList.Count > Task.Output.Count Then Throw New Exception("整合包中的部分 Mod 版本已被 Mod 作者删除,所以没法继续安装了,请向整合包作者反馈该问题")
             End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s

+ 1 - 4
Plain Craft Launcher 2/Modules/ModSecret.vb

@@ -1,6 +1,5 @@
 '由于包含加解密等安全信息,本文件中的部分代码已被删除
 
-Imports System.Net.Http
 Imports System.Security.Cryptography
 
 Friend Module ModSecret
@@ -14,7 +13,6 @@ Friend Module ModSecret
     'CurseForge API Key
     Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "")
 
-
     Friend Sub SecretOnApplicationStart()
         '提升 UI 线程优先级
         Thread.CurrentThread.Priority = ThreadPriority.Highest
@@ -73,7 +71,7 @@ Friend Module ModSecret
 
 #Region "网络鉴权"
 
-    Friend Function SecretCdnSign(UrlWithMark As String)
+    Friend Function SecretCdnSign(UrlWithMark As String) As String
         If Not UrlWithMark.EndsWithF("{CDN}") Then Return UrlWithMark
         Return UrlWithMark.Replace("{CDN}", "").Replace(" ", "%20")
     End Function
@@ -92,7 +90,6 @@ Friend Module ModSecret
         If Url.Contains("api.curseforge.com") Then Req.Headers.Add("x-api-key", CurseForgeAPIKey)
     End Sub
 
-
 #End Region
 
 #Region "字符串加解密"

+ 0 - 59
Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb

@@ -1,59 +0,0 @@
-Imports System.Net
-
-Public Class CookieWebClient
-    Inherits WebClient
-
-    Public Sub New(container As CookieContainer, Headers As Dictionary(Of String, String))
-        Me.New(container)
-        For Each keyVal In Headers
-            Me.Headers(keyVal.Key) = keyVal.Value
-        Next
-    End Sub
-
-    Public Sub New()
-        Me.New(New CookieContainer())
-    End Sub
-
-    Public Sub New(container As CookieContainer)
-        Me.container = container
-    End Sub
-
-    Private Shadows ReadOnly container As New CookieContainer()
-
-    ''' <summary>
-    ''' 以毫秒为单位的超时。
-    ''' </summary>
-    Public Timeout As Integer = 600000
-
-    Protected Overrides Function GetWebRequest(address As Uri) As WebRequest
-        Dim r As WebRequest = MyBase.GetWebRequest(address)
-        Dim request = TryCast(r, HttpWebRequest)
-
-        If request IsNot Nothing Then
-            request.CookieContainer = container
-            request.Timeout = Timeout
-        End If
-
-        Return r
-    End Function
-
-    Protected Overrides Function GetWebResponse(request As WebRequest, result As IAsyncResult) As WebResponse
-        Dim response As WebResponse = MyBase.GetWebResponse(request, result)
-        ReadCookies(response)
-        Return response
-    End Function
-
-    Protected Overrides Function GetWebResponse(request As WebRequest) As WebResponse
-        Dim response As WebResponse = MyBase.GetWebResponse(request)
-        ReadCookies(response)
-        Return response
-    End Function
-
-    Private Sub ReadCookies(r As WebResponse)
-        Dim response = TryCast(r, HttpWebResponse)
-        If response IsNot Nothing Then
-            Dim cookies As CookieCollection = response.Cookies
-            container.Add(cookies)
-        End If
-    End Sub
-End Class

+ 22 - 23
Plain Craft Launcher 2/Modules/Base/ModDragHelper.vb → Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb

@@ -1,11 +1,10 @@
-Imports System
+'来源:https://blog.csdn.net/simpleman2000/article/details/140952294
+
 Imports System.ComponentModel
 Imports System.Runtime.InteropServices
-Imports System.Text
 Imports System.Windows.Interop
 
-
-Public Class DragFileHelper
+Public Class DragHelper
 
     Public Event DragDrop As EventHandler
 
@@ -32,22 +31,22 @@ Public Class DragFileHelper
     Public Property HwndIntPtrSource As HwndSource
 
     Public Sub AddHook()
-        Me.RemoveDragHook()
-        Me.HwndIntPtrSource.AddHook(AddressOf WndProc)
-        Dim handle As IntPtr = Me.HwndIntPtrSource.Handle
+        RemoveDragHook()
+        HwndIntPtrSource.AddHook(AddressOf WndProc)
+        Dim handle As IntPtr = HwndIntPtrSource.Handle
         If IsUserAnAdmin() Then RevokeDragDrop(handle)
         DragAcceptFiles(handle, True)
         ChangeMessageFilter(handle)
     End Sub
 
     Public Sub RemoveDragHook()
-        Me.HwndIntPtrSource.RemoveHook(AddressOf WndProc)
-        DragAcceptFiles(Me.HwndIntPtrSource.Handle, False)
+        HwndIntPtrSource.RemoveHook(AddressOf WndProc)
+        DragAcceptFiles(HwndIntPtrSource.Handle, False)
     End Sub
 
-    Private Function WndProc(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr, ByRef handled As Boolean) As IntPtr
+    Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr
         Dim filePaths As String() = Nothing
-        Dim point As POINT = New POINT()
+        Dim point As New POINT()
 
         If TryGetDropInfo(msg, wParam, filePaths, point) Then
             DropPoint = point
@@ -59,31 +58,31 @@ Public Class DragFileHelper
     End Function
 
     <DllImport("user32.dll", SetLastError:=True)>
-    Private Shared Function ChangeWindowMessageFilterEx(ByVal hWnd As IntPtr, ByVal msg As UInteger, ByVal action As UInteger, ByRef pChangeFilterStruct As CHANGEFILTERSTRUCT) As Boolean
+    Private Shared Function ChangeWindowMessageFilterEx(hWnd As IntPtr, msg As UInteger, action As UInteger, ByRef pChangeFilterStruct As CHANGEFILTERSTRUCT) As Boolean
     End Function
 
     <DllImport("user32.dll", SetLastError:=True)>
-    Private Shared Function ChangeWindowMessageFilter(ByVal msg As UInteger, ByVal flags As UInteger) As Boolean
+    Private Shared Function ChangeWindowMessageFilter(msg As UInteger, flags As UInteger) As Boolean
     End Function
 
     <DllImport("shell32.dll")>
-    Private Shared Sub DragAcceptFiles(ByVal hWnd As IntPtr, ByVal fAccept As Boolean)
+    Private Shared Sub DragAcceptFiles(hWnd As IntPtr, fAccept As Boolean)
     End Sub
 
     <DllImport("shell32.dll", CharSet:=CharSet.Unicode)>
-    Private Shared Function DragQueryFile(ByVal hWnd As IntPtr, ByVal iFile As UInteger, ByVal lpszFile As StringBuilder, ByVal cch As Integer) As UInteger
+    Private Shared Function DragQueryFile(hWnd As IntPtr, iFile As UInteger, lpszFile As StringBuilder, cch As Integer) As UInteger
     End Function
 
     <DllImport("shell32.dll")>
-    Private Shared Function DragQueryPoint(ByVal hDrop As IntPtr, ByRef lppt As POINT) As Boolean
+    Private Shared Function DragQueryPoint(hDrop As IntPtr, ByRef lppt As POINT) As Boolean
     End Function
 
     <DllImport("shell32.dll")>
-    Private Shared Sub DragFinish(ByVal hDrop As IntPtr)
+    Private Shared Sub DragFinish(hDrop As IntPtr)
     End Sub
 
     <DllImport("ole32.dll")>
-    Private Shared Function RevokeDragDrop(ByVal hWnd As IntPtr) As Integer
+    Private Shared Function RevokeDragDrop(hWnd As IntPtr) As Integer
     End Function
 
     <DllImport("shell32.dll")>
@@ -109,13 +108,13 @@ Public Class DragFileHelper
     Private Const MSGFLT_ADD As UInteger = 1
     Private Const MAX_PATH As Integer = 260
 
-    Private Shared Sub ChangeMessageFilter(ByVal handle As IntPtr)
+    Private Shared Sub ChangeMessageFilter(handle As IntPtr)
         Dim ver As Version = Environment.OSVersion.Version
         Dim isVistaOrHigher As Boolean = ver >= New Version(6, 0)
         Dim isNt61OrHiger As Boolean = ver >= New Version(6, 1)
 
         If isVistaOrHigher Then
-            Dim status As CHANGEFILTERSTRUCT = New CHANGEFILTERSTRUCT With {.cbSize = 8}
+            Dim status As New CHANGEFILTERSTRUCT With {.cbSize = 8}
             For Each msg As UInteger In New UInteger() {WM_DROPFILES, WM_COPYGLOBALDATA, WM_COPYDATA}
                 Dim [error] As Boolean = False
                 If isNt61OrHiger Then
@@ -129,7 +128,7 @@ Public Class DragFileHelper
         End If
     End Sub
 
-    Private Shared Function TryGetDropInfo(ByVal msg As Integer, ByVal wParam As IntPtr, ByRef dropFilePaths As String(), ByRef dropPoint As POINT) As Boolean
+    Private Shared Function TryGetDropInfo(msg As Integer, wParam As IntPtr, ByRef dropFilePaths As String(), ByRef dropPoint As POINT) As Boolean
         dropFilePaths = Nothing
         dropPoint = New POINT()
 
@@ -141,7 +140,7 @@ Public Class DragFileHelper
         For i As UInteger = 0 To fileCount - 1
             Dim sb As New StringBuilder(MAX_PATH)
             Dim result As UInteger = DragQueryFile(wParam, i, sb, sb.Capacity)
-            If result > 0 Then dropFilePaths(CInt(i)) = sb.ToString()
+            If result > 0 Then dropFilePaths(i) = sb.ToString()
         Next
 
         DragQueryPoint(wParam, dropPoint)
@@ -149,4 +148,4 @@ Public Class DragFileHelper
         Return True
     End Function
 
-End Class
+End Class

+ 2 - 2
Plain Craft Launcher 2/My Project/AssemblyInfo.vb

@@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices
 ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
 ' 方法是按如下所示使用“*”
 
-<Assembly: AssemblyVersion("2.10.3.0")>
-<Assembly: AssemblyFileVersion("2.10.3.0")>
+<Assembly: AssemblyVersion("2.10.4.0")>
+<Assembly: AssemblyFileVersion("2.10.4.0")>
 <Assembly: NeutralResourcesLanguage("")>

+ 53 - 13
Plain Craft Launcher 2/My Project/Resources.Designer.vb

@@ -63,9 +63,29 @@ Namespace My.Resources
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
-        Friend ReadOnly Property Custom() As Byte()
+        Friend ReadOnly Property CacheCow_Client() As Byte()
             Get
-                Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("CacheCow.Client", resourceCulture)
+                Return CType(obj,Byte())
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  查找 System.Byte[] 类型的本地化资源。
+        '''</summary>
+        Friend ReadOnly Property CacheCow_Client_FileStore() As Byte()
+            Get
+                Dim obj As Object = ResourceManager.GetObject("CacheCow.Client.FileStore", resourceCulture)
+                Return CType(obj,Byte())
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  查找 System.Byte[] 类型的本地化资源。
+        '''</summary>
+        Friend ReadOnly Property CacheCow_Common() As Byte()
+            Get
+                Dim obj As Object = ResourceManager.GetObject("CacheCow.Common", resourceCulture)
                 Return CType(obj,Byte())
             End Get
         End Property
@@ -73,9 +93,9 @@ Namespace My.Resources
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
-        Friend ReadOnly Property Dialogs() As Byte()
+        Friend ReadOnly Property Custom() As Byte()
             Get
-                Dim obj As Object = ResourceManager.GetObject("Dialogs", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture)
                 Return CType(obj,Byte())
             End Get
         End Property
@@ -105,7 +125,7 @@ Namespace My.Resources
         '''</summary>
         Friend ReadOnly Property Imazen_WebP() As Byte()
             Get
-                Dim obj As Object = ResourceManager.GetObject("Imazen_WebP", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("Imazen.WebP", resourceCulture)
                 Return CType(obj,Byte())
             End Get
         End Property
@@ -123,9 +143,9 @@ Namespace My.Resources
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
-        Friend ReadOnly Property Json() As Byte()
+        Friend ReadOnly Property libwebp64() As Byte()
             Get
-                Dim obj As Object = ResourceManager.GetObject("Json", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("libwebp64", resourceCulture)
                 Return CType(obj,Byte())
             End Get
         End Property
@@ -133,9 +153,9 @@ Namespace My.Resources
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
-        Friend ReadOnly Property libwebp64() As Byte()
+        Friend ReadOnly Property ModData() As Byte()
             Get
-                Dim obj As Object = ResourceManager.GetObject("libwebp64", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("ModData", resourceCulture)
                 Return CType(obj,Byte())
             End Get
         End Property
@@ -143,9 +163,9 @@ Namespace My.Resources
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
-        Friend ReadOnly Property ModData() As Byte()
+        Friend ReadOnly Property NAudio() As Byte()
             Get
-                Dim obj As Object = ResourceManager.GetObject("ModData", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("NAudio", resourceCulture)
                 Return CType(obj,Byte())
             End Get
         End Property
@@ -153,9 +173,29 @@ Namespace My.Resources
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
-        Friend ReadOnly Property NAudio() As Byte()
+        Friend ReadOnly Property Newtonsoft_Json() As Byte()
             Get
-                Dim obj As Object = ResourceManager.GetObject("NAudio", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("Newtonsoft.Json", resourceCulture)
+                Return CType(obj,Byte())
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  查找 System.Byte[] 类型的本地化资源。
+        '''</summary>
+        Friend ReadOnly Property Ookii_Dialogs_Wpf() As Byte()
+            Get
+                Dim obj As Object = ResourceManager.GetObject("Ookii.Dialogs.Wpf", resourceCulture)
+                Return CType(obj,Byte())
+            End Get
+        End Property
+        
+        '''<summary>
+        '''  查找 System.Byte[] 类型的本地化资源。
+        '''</summary>
+        Friend ReadOnly Property System_Net_Http_Formatting() As Byte()
+            Get
+                Dim obj As Object = ResourceManager.GetObject("System.Net.Http.Formatting", resourceCulture)
                 Return CType(obj,Byte())
             End Get
         End Property

+ 19 - 7
Plain Craft Launcher 2/My Project/Resources.resx

@@ -118,27 +118,30 @@
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="CacheCow.Client" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\CacheCow.Client.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="CacheCow.Client.FileStore" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\CacheCow.Client.FileStore.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="CacheCow.Common" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\CacheCow.Common.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
   <data name="Custom" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\resources\custom.xaml;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="Dialogs" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\resources\ookii.dialogs.wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
   <data name="ForgeInstaller" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\forge-installer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   <data name="Help" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Help.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="Imazen_WebP" type="System.Resources.ResXFileRef, System.Windows.Forms">
+  <data name="Imazen.WebP" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Imazen.WebP.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   <data name="JavaWrapper" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\java-wrapper.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="Json" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
   <data name="libwebp64" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\libwebp64.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
@@ -148,6 +151,15 @@
   <data name="NAudio" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\NAudio.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
+  <data name="Newtonsoft.Json" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="Ookii.Dialogs.Wpf" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\resources\ookii.dialogs.wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="System.Net.Http.Formatting" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\System.Net.Http.Formatting.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
   <data name="Transformer" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\transformer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>

+ 1 - 1
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml

@@ -92,4 +92,4 @@
             <local:MyLoading Text="正在获取XXX列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
         </local:MyCard>
     </StackPanel>
-</local:MyScrollViewer>
+</local:MyScrollViewer>

+ 0 - 1
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb

@@ -99,7 +99,6 @@ Public Class PageComp
             End If
             TargetVersion = Nothing
             If TargetName IsNot Nothing Then
-                '设置搜索框内容
                 TextSearchName.Text = TargetName
                 TargetName = Nothing
             End If

+ 10 - 15
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb

@@ -73,13 +73,14 @@
     '筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表)
     Private Function GetResults() As List(Of CompFile)
         Dim Results As List(Of CompFile) = CompFileLoader.Output
-        If PageType = CompType.Any Then
-            Results = Results.Where(Function(r) r.Type <> CompType.Plugin).ToList
-        ElseIf PageType = CompType.Shader OrElse PageType = CompType.ResourcePack Then
-            Results = Results.ToList
-        Else
-            Results = Results.Where(Function(r) r.Type = PageType).ToList
-        End If
+        Select Case PageType
+            Case CompType.Any
+                Results = Results.Where(Function(r) r.Type <> CompType.Plugin).ToList
+            Case CompType.Shader, CompType.ResourcePack
+                '不筛选光影和资源包,否则原版光影会因为是资源包格式而被过滤(#6473)
+            Case Else
+                Results = Results.Where(Function(r) r.Type = PageType).ToList
+        End Select
         Return Results
     End Function
     Private Sub Load_OnFinish()
@@ -358,7 +359,7 @@
                         Return False
                     End Function
                     '获取常规资源默认下载位置
-                    If CachedFolder.ContainsKey(Project.Type) AndAlso Not String.IsNullOrEmpty(CachedFolder(Project.Type)) Then
+                    If CachedFolder.ContainsKey(Project.Type) Then
                         DefaultFolder = CachedFolder(Project.Type)
                         Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}")
                     ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
@@ -424,13 +425,7 @@
                     If Not Target.Contains("\") Then Return
                     '构造步骤加载器
                     Dim LoaderName As String = Desc & "下载:" & GetFileNameWithoutExtentionFromPath(Target) & " "
-                    If Target <> DefaultFolder Then
-                        If CachedFolder.ContainsKey(Project.Type) Then
-                            CachedFolder(Project.Type) = GetPathFromFullPath(Target)
-                        Else
-                            CachedFolder.Add(Project.Type, GetPathFromFullPath(Target))
-                        End If
-                    End If
+                    If Target <> DefaultFolder Then CachedFolder(Project.Type) = GetPathFromFullPath(Target)
                     Dim Loaders As New List(Of LoaderBase)
                     Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True})
                     '启动

+ 30 - 27
Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb

@@ -570,7 +570,8 @@ pause"
             '官方源
             Dim PageData As String
             Try
-                PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
+                PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile,
+                    Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True)
                 Task.Progress = 0.8
                 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
                 Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -733,7 +734,8 @@ Retry:
             '官方源
             Dim PageData As String
             Try
-                PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
+                PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile,
+                    Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True)
                 Task.Progress = 0.8
                 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
                 Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -1340,7 +1342,7 @@ Retry:
                     If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then
                         '下载原版 Json 文件
                         Task.Progress = 0.4
-                        Dim RawJson As JObject = GetJson(NetGetCodeByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True))
+                        Dim RawJson As JObject = GetJson(NetRequestByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True))
                         '[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg]
                         Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).BeforeFirst("@")
                         Dim Address = McLibGet(OriginalName).Replace(".jar", "-mappings." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1))
@@ -1641,30 +1643,31 @@ Retry:
     Public Sub McDownloadForgeRecommendedRefresh()
         If IsForgeRecommendedRefreshed Then Return
         IsForgeRecommendedRefreshed = True
-        RunInNewThread(Sub()
-                           Try
-                               Log("[Download] 刷新 Forge 推荐版本缓存开始")
-                               Dim Result As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/forge/promos")
-                               If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")")
-                               Dim ResultJson As JContainer = GetJson(Result)
-                               '获取所有推荐版本列表
-                               Dim RecommendedList As New List(Of String)
-                               For Each Version As JObject In ResultJson
-                                   If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For
-                                   Dim Name As String = Version("name")
-                                   If Not Name.EndsWithF("-recommended") Then Continue For
-                                   '内容为:"1.15.2":"31.2.0"
-                                   RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """"))
-                               Next
-                               If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")")
-                               '保存
-                               Dim CacheJson As String = "{" & Join(RecommendedList, ",") & "}"
-                               WriteFile(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson)
-                               Log("[Download] 刷新 Forge 推荐版本缓存成功")
-                           Catch ex As Exception
-                               Log(ex, "刷新 Forge 推荐版本缓存失败")
-                           End Try
-                       End Sub, "ForgeRecommendedRefresh")
+        RunInNewThread(
+        Sub()
+            Try
+                Log("[Download] 刷新 Forge 推荐版本缓存开始")
+                Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos")
+                If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")")
+                Dim ResultJson As JContainer = GetJson(Result)
+                '获取所有推荐版本列表
+                Dim RecommendedList As New List(Of String)
+                For Each Version As JObject In ResultJson
+                    If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For
+                    Dim Name As String = Version("name")
+                    If Not Name.EndsWithF("-recommended") Then Continue For
+                    '内容为:"1.15.2":"31.2.0"
+                    RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """"))
+                Next
+                If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")")
+                '保存
+                Dim CacheJson As String = "{" & Join(RecommendedList, ",") & "}"
+                WriteFile(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson)
+                Log("[Download] 刷新 Forge 推荐版本缓存成功")
+            Catch ex As Exception
+                Log(ex, "刷新 Forge 推荐版本缓存失败")
+            End Try
+        End Sub, "ForgeRecommendedRefresh")
     End Sub
     Private IsForgeRecommendedRefreshed As Boolean = False
 

+ 1 - 2
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml

@@ -27,8 +27,7 @@
                         <local:MyHint Text="必须安装 OptiFabric 才能正常使用 OptiFine!" Margin="0,10,0,0" x:Name="HintOptiFabric" Theme="Red" />
                         <local:MyHint Text="安装结束后,请在 Mod 下载中搜索 OptiFabric Origins 并下载,否则 OptiFine 会无法使用!" Margin="0,10,0,0" x:Name="HintOptiFabricOld" Theme="Yellow" />
                         <local:MyHint Text="OptiFine 与一部分 Mod 的兼容性不佳,请谨慎安装。" Margin="0,10,0,0" x:Name="HintModOptiFine" Theme="Yellow" />
-						<local:MyHint Text="1.20.5+ 没有 OptiFabric,无法使用 OptiFine,如需加载光影请考虑使用其他 Mod" Margin="0,10,0,0" x:Name="HintModOptiFineHigh" Theme="Blue" />
-						<local:MyCard Title="Forge" Height="40" Margin="0,12,0,0" x:Name="CardForge" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
+                        <local:MyCard Title="Forge" Height="40" Margin="0,12,0,0" x:Name="CardForge" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
                         <StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanForge" />
                         <Grid x:Name="PanForgeInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>

+ 10 - 9
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb

@@ -476,11 +476,6 @@
         Else
             HintModOptiFine.Visibility = Visibility.Collapsed
         End If
-        If SelectedFabric IsNot Nothing AndAlso VersionSortInteger(SelectedMinecraftId, "1.21.4") > 0 Then
-            HintModOptiFineHigh.Visibility = Visibility.Visible
-        Else
-            HintModOptiFineHigh.Visibility = Visibility.Collapsed
-        End If
         '结束
         IsReloading = False
     End Sub
@@ -676,13 +671,16 @@
         If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容"
         If LoadOptiFine Is Nothing OrElse LoadOptiFine.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
         If LoadOptiFine.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadOptiFine.State, Object).Error.Message
-        '检查 Fabric 1.20.5+:没有 OptiFabric 故全部不兼容
-        If SelectedFabric IsNot Nothing AndAlso VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then Return "与 Fabric 不兼容"
         '检查 Forge 1.13 - 1.14.3:全部不兼容
         If SelectedForge IsNot Nothing AndAlso
             VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then
             Return "与 Forge 不兼容"
         End If
+        '检查 Fabric 1.20.5+:全部不兼容
+        If SelectedFabric IsNot Nothing AndAlso
+            VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then
+            Return "与 Fabric 不兼容"
+        End If
         '检查 Forge 版本
         Dim HasAny As Boolean = False
         Dim HasRequiredVersion As Boolean = False
@@ -988,8 +986,11 @@
     Private Function LoadFabricGetError() As String
         If LoadFabric Is Nothing OrElse LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
         If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabric.State, Object).Error.Message
-        '检查 Fabric 1.20.5+:没有 OptiFabric 故全部不兼容
-        If SelectedOptiFine IsNot Nothing AndAlso VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then Return "与 OptiFine 不兼容"
+        '检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容
+        If SelectedOptiFine IsNot Nothing AndAlso
+            VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then
+            Return "与 OptiFine 不兼容"
+        End If
         For Each Version As JObject In DlFabricListLoader.Output.Value("game")
             If Version("version").ToString = SelectedMinecraftId.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") Then
                 If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容"

+ 11 - 9
Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb

@@ -105,13 +105,15 @@
         Dim UnknownFailureCount As Integer = 0
         Do While Not MyConverter.IsExited
             Try
-                Dim Result = NetRequestOnce(
-                    "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", "POST",
-                    "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" &
-                    "client_id=" & OAuthClientId & "&" &
-                    "device_code=" & DeviceCode & "&" &
-                    "scope=XboxLive.signin%20offline_access",
-                    "application/x-www-form-urlencoded", 5000 + UnknownFailureCount * 5000, MakeLog:=False)
+                Dim Result = NetRequestByClient(
+                    "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", HttpMethod.Post,
+                    Content:=
+                        "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" &
+                        "client_id=" & OAuthClientId & "&" &
+                        "device_code=" & DeviceCode & "&" &
+                        "scope=XboxLive.signin%20offline_access",
+                    ContentType:="application/x-www-form-urlencoded",
+                    Timeout:=5000 + UnknownFailureCount * 5000)
                 '获取结果
                 Dim ResultJson As JObject = GetJson(Result)
                 McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒")
@@ -125,8 +127,8 @@
                 ElseIf ex.Message.Contains("expired_token") Then
                     Finished(New Exception("$登录用时太长啦,重新试试吧!"))
                     Return
-                Else If ex.Message.Contains("Account security interrupt") Then
-                    Finished(New Exception("$非常抱歉,该账号由于安全问题无法登陆,请前往 Microsoft 账户页获取更多信息。"))
+                ElseIf ex.Message.Contains("Account security interrupt") Then
+                    Finished(New Exception("$该账号由于安全问题无法登陆,请前往微软账户页获取更多信息。"))
                     Return
                 ElseIf ex.Message.Contains("service abuse") Then
                     Finished(New Exception("$非常抱歉,该账号已被微软封禁,无法登录。"))

+ 6 - 5
Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb

@@ -259,12 +259,13 @@ Retry:
                 End Sub)
                 If SelId Is Nothing Then Return
                 '发送请求
-                Dim Result As String = NetRequestRetry("https://api.minecraftservices.com/minecraft/profile/capes/active",
-                    If(SelId = 0, "DELETE", "PUT"),
-                    If(SelId = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelId - 1)("id"))).ToString(0)),
-                    "application/json", Headers:=New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
+                Dim Result As String = NetRequestByClientRetry("https://api.minecraftservices.com/minecraft/profile/capes/active",
+                    If(SelId = 0, HttpMethod.Delete, HttpMethod.Put),
+                    Content:=If(SelId = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelId - 1)("id"))).ToString(0)),
+                    ContentType:="application/json",
+                    Headers:={{"Authorization", "Bearer " & AccessToken}})
                 If Result.Contains("""errorMessage""") Then
-                    Hint("更改披风失败:" & GetJson(Result)("errorMessage"), HintType.Critical)
+                    Hint("更改披风失败:" & GetJson(Result)("errorMessage").ToString, HintType.Critical)
                     Return
                 Else
                     Hint("更改披风成功!", HintType.Finish)

+ 5 - 3
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb

@@ -156,7 +156,7 @@ Download:
             Dim Version As String = ""
             Dim NeedDownload As Boolean = True
             Try
-                Version = NetGetCodeByRequestOnce(VersionAddress, Timeout:=10000)
+                Version = NetRequestByClientRetry(VersionAddress)
                 If Version.Length > 1000 Then Throw New Exception($"获取的主页版本过长({Version.Length} 字符)")
                 Dim CurrentVersion As String = Setup.Get("CacheSavedPageVersion")
                 If Version <> "" AndAlso CurrentVersion <> "" AndAlso Version = CurrentVersion Then
@@ -171,7 +171,7 @@ Download:
             End Try
             '实际下载
             If NeedDownload Then
-                Dim FileContent As String = NetGetCodeByRequestRetry(Address)
+                Dim FileContent As String = NetRequestByClientRetry(Address)
                 Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}")
                 Setup.Set("CacheSavedPageUrl", Address)
                 Setup.Set("CacheSavedPageVersion", Version)
@@ -231,7 +231,9 @@ Download:
             Try
                 '修改时应同时修改 PageOtherHelpDetail.Init
                 Content = HelpArgumentReplace(Content)
-                If Content.Contains("xmlns") Then Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
+                Do While Content.Contains("xmlns")
+                    Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
+                Loop
                 Content = "<StackPanel xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:sys=""clr-namespace:System;assembly=mscorlib"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:local=""clr-namespace:PCL;assembly=Plain Craft Launcher 2"">" & Content & "</StackPanel>"
                 Log($"[Page] 实例化:加载主页 UI 开始,最终内容长度:{Content.Count}")
                 PanCustom.Children.Add(GetObjectFromXML(Content))

+ 1 - 1
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml

@@ -5,7 +5,7 @@
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
       xmlns:local="clr-namespace:PCL"
       mc:Ignorable="d" d:DesignWidth="297.6">
-    <StackPanel Margin="0,0,0,5">
+    <StackPanel Margin="0,0,0,5" x:Name="PanMain">
         <local:MyHint x:Name="HintChinese" Theme="Red" Text="玩家名如果包含英文、数字、下划线以外的内容,可能会无法进入 Minecraft 1.18 以后的世界!" Margin="0,0,0,15" Visibility="Collapsed" />
         <local:MySkin HorizontalAlignment="Center" x:Name="Skin" HasCape="False" Margin="0,0,0,15" />
         <local:MyComboBox IsEditable="True" x:Name="ComboName" Height="28" IsTextSearchEnabled="False" HintText="游戏用户名" />

+ 1 - 0
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb

@@ -64,6 +64,7 @@ Public Class PageLoginLegacy
     Private Sub ComboLegacy_TextChanged(sender As Object, e As TextChangedEventArgs) Handles ComboName.TextChanged
         If Setup.Get("LaunchSkinType") = 0 Then PageLaunchLeft.SkinLegacy.Start(IsForceRestart:=True)
         HintChinese.Visibility = If(RegexCheck(ComboName.Text, "^[0-9A-Za-z_]*$"), Visibility.Collapsed, Visibility.Visible)
+        RunInUi(Sub() PanMain.InvalidateMeasure(), True) '由于 WPF 的 Bug,它不一定会自动更新 HintChinese 的大小(#6627)
     End Sub
     Private Sub Skin_Click() Handles Skin.Click
         If (Setup.Get("UiHiddenPageSetup") OrElse Setup.Get("UiHiddenSetupLaunch")) AndAlso Not PageSetupUI.HiddenForceShow Then

+ 4 - 2
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml

@@ -23,9 +23,11 @@
                     <local:MyIconButton.ContextMenu>
                         <ContextMenu Closed="HidePanel" HorizontalOffset="10" VerticalOffset="18">
                             <local:MyMenuItem Click="BtnSkinEdit_Click" Header="修改皮肤"/>
-                            <local:MyMenuItem Click="BtnSkinSave_Click" Header="保存皮肤"/>
-                            <local:MyMenuItem Click="BtnSkinRefresh_Click" Header="刷新头像"/>
+                            <local:MyMenuItem Click="BtnSkinSave_Click" Header="保存皮肤文件"/>
+                            <Separator />
                             <local:MyMenuItem Click="BtnSkinCape_Click" Header="修改披风"/>
+                            <Separator />
+                            <local:MyMenuItem Click="BtnSkinRefresh_Click" Header="刷新"/>
                         </ContextMenu>
                     </local:MyIconButton.ContextMenu>
                 </local:MyIconButton>

+ 1 - 1
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb

@@ -107,7 +107,7 @@ Retry:
                     McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True)
                     GoTo Retry
                 ElseIf Result.Contains("""error""") Then
-                    Hint("更改皮肤失败:" & GetJson(Result)("error"), HintType.Critical)
+                    Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Critical)
                     Return
                 End If
                 '获取新皮肤地址

+ 491 - 468
Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml

@@ -1,468 +1,491 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-    mc:Ignorable="d" x:Class="PageOtherAbout"
-    PanScroll="{Binding ElementName=PanBack}" RenderOptions.BitmapScalingMode="LowQuality">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel x:Name="PanMain" Margin="25,10" Grid.IsSharedSizeScope="True">
-            <local:MyCard Margin="0,15" Title="关于">
-                <Grid Margin="21,40,21,16">
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                    </Grid.RowDefinitions>
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="40" />
-                        <ColumnDefinition />
-                        <ColumnDefinition Width="150" />
-                    </Grid.ColumnDefinitions>
-                    <local:MyListItem IsHitTestVisible="False" Title="龙腾猫跃" Margin="-5,0,15,0" Info="Plain Craft Launcher 的作者!" Grid.Row="0" Grid.Column="1" />
-                    <local:MyListItem IsHitTestVisible="False" Title="Plain Craft Launcher 2" Margin="-5,0,15,0" Info="当前版本:%VERSION%(%VERSIONCODE%.%BRANCH%)" Grid.Row="1" Grid.Column="1" x:Name="ItemAboutPcl" />
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Logo.png" Grid.Row="1" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/LTCat.jpg" Grid.Row="0" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <local:MyButton Text="赞助作者" Grid.Column="2" Grid.Row="0" Height="35" EventType="打开网页" EventData="https://afdian.com/a/LTCat" />
-                    <local:MyButton Text="查看源代码" Grid.Column="2" Grid.Row="1" Height="35" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL" />
-                </Grid>
-            </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="特别鸣谢">
-                <Grid Margin="21,40,21,16">
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                        <RowDefinition Height="45" />
-                    </Grid.RowDefinitions>
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="40" />
-                        <ColumnDefinition />
-                        <ColumnDefinition Width="150" />
-                    </Grid.ColumnDefinitions>
-                    <local:MyListItem Title="bangbang93" Margin="-5,0,15,0" IsHitTestVisible="False" Info="提供 BMCLAPI 镜像源和 Forge 安装工具,详见 https://bmclapi.bangbang93.com" Grid.Row="0" Grid.Column="1" />
-                    <local:MyButton x:Name="BtnAboutBmclapi" Text="赞助镜像源" Grid.Row="0" Grid.Column="2" Height="35" />
-                    <local:MyListItem Title="MC 百科" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Mod 名称的中文翻译和更多相关信息!" Grid.Row="1" Grid.Column="1" />
-                    <local:MyButton x:Name="BtnAboutWiki" Text="打开百科" Grid.Row="1" Grid.Column="2" Height="35" />
-                    <local:MyListItem Title="00ll00" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Java Launch Wrapper 和一些重要服务支持!" Grid.Row="2" Grid.Column="1" />
-                    <local:MyListItem Title="z0z0r4" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 MCIM 中国 Mod 下载镜像源和帮助库图床!" Grid.Row="3" Grid.Column="1" />
-                    <local:MyListItem Title="Patrick" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="设计并制作了 PCL 图标,让我从做图标的水深火热中得到了解脱……" Grid.Row="4" Grid.Column="1" />
-                    <local:MyListItem Title="Hao_Tian" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="在内测中找出了一大堆没人想得到的诡异 Bug,有非同寻常的 Bug 体质" Grid.Row="5" Grid.Column="1" />
-                    <local:MyListItem Title="Minecraft 中文论坛" Margin="-5,0,15,0" IsHitTestVisible="False" Info="虽然已经关站了,但感谢此前提供了 MCBBS 镜像源……" Grid.Row="6" Grid.Column="1" />
-                    <local:MyListItem Title="PCL 内群的各位" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="感谢内群的沙雕网友们这么久以来对我和 PCL 的支持与鼓励!" Grid.Row="7" Grid.Column="1" />
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/bangbang93.png" Grid.Row="0" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/wiki.png" Grid.Row="1" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/00ll00.png" Grid.Row="2" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/z0z0r4.png" Grid.Row="3" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Patrick.png" Grid.Row="4" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Hao_Tian.jpg" Grid.Row="5" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/MCBBS.png" Grid.Row="6" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/PCL2.png" Grid.Row="7" Margin="3">
-                        <Image.Clip>
-                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
-                        </Image.Clip>
-                    </Image>
-                </Grid>
-            </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="赞助者">
-                <StackPanel Margin="25,40,10,20">
-                    <TextBlock HorizontalAlignment="Left" Text="以下是 2025 年 5 月的高级赞助者们!感谢各位对 PCL 和伟大的咕咕咕事业的支持!" Margin="0,0,25,5.5" TextTrimming="None" TextWrapping="WrapWithOverflow" />
-                    <WrapPanel Margin="0,0,0,10">
-                        <TextBlock Width="120" Margin="0,1.5" Text="Alliance" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="风之轻语" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="大博文" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="狐狸不吃酸葡萄" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="仰望星空的牧星人" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="某个网友" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="114514" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Emmanuel" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="云漫雾影" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="**葵" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="吴同学" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Anderson521314" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Alrosa" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="可爱电鳗" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小野冢小町" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="田坎村西头王二狗" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="令月廿一" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Mrdandan" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="111" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="27733" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="韦韦神父" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="土豆" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="隔岸流年" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="冥吻" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="向阳" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="轻浮" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小涵0v0" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="祈愿小狐" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="爱莉爱莉爱" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Bydold" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="SMRWXH" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="翷月" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="husky" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Hz_yuki" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="先知" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="2683672592" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="龖龖" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="陨树" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Gripen" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="棒槌" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="北北ovo" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="TombName" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="莳倾梦のshiqingmeng" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Bortor" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="OuO6006" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="MKSeKai" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="清尘墨雨" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="墨染柊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="福福" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="夕尽暮染" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="光年摆渡人" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="梦清韩" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="孤城辞典" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="灰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小周周" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="孤星痕" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="月孤" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="团团" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="J_ONE" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="初尘ya" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="MooBHise" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="凌之芬芳" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="土豆回锅土豆" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Reisen" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Rong" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="FiZone" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="橡素" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="逆光の思" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Edifier" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="迭风" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="mumu" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="欧润几" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="SiberianHusky" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="虫" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="闪电" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Bob_Marcus" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="瘦瘦爱吃呕吼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="盛夏" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="飞起来的Mine" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="cc" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="简麟" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="郴州火车迷" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="守白" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="天长地忙" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="月无痕" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="HWW" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="樱岛麻衣" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="桌子" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="bushihuankong" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="戏梦simi" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Metal_Yoshika" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="H+H" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="junhuigege" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Kallen" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="x" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="随便玩" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="魇夜星渊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="taomiao13579" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="无敌大鱼" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="忆兀安" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="无梦" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="SlyBlackCat" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="180" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="总督大人" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="sc99" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Fumi" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="豪放" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Canqiu" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="朱利安" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="白露辰霜" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="点燃心中火的大音乐家" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="度你入魔" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小宝" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="无" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="青出梦名" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雾日斋主人" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="鸣纳" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="苏卜辰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="3827" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="水里游的老鹰" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雪桜" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Yukiteru" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="零依" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="玻璃糖罐" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="JFF_2005" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="流星" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ccc" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="PennyLee2023" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="伯劳" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Sy5H" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Fomalhaut" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="PERLICA" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="北冥鱼吃" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="千万别自闭" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="00号玩家" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="亗竹" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="云一梦" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="久念" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="继晷" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="史蒂夫" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="椎罗太难了" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="DiamondSYP" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="木林西" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雩月" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="6628" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="ZoCc" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="wan_yao" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="White_c" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="尉迟羽晨" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="栀梓" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小命" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="金" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="倾墨画君颜" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="南宫若兮" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="沐言" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="傻预备" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="蒸木" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="那菈" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="suanqiu" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="人见人爱的熊熊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Furan255" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="MachineCrab" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="五只大花狗" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="威尔特" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="StarNCosmos" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Eason" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="风之幻城" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="王舒晨" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="H" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="咏夕" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="不及君子柒" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="GUAN_SKD" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雅柏菲卡" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="AtDusk" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="九月想睡觉" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="六七" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="老登" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="wd_xuan" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Wake" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Jannik" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="cybone" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="mc12345zhl123" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="解" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Akights" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="秋刀驴" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="樟涛竹" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="sikuno" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="鬼目" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="拜拜白" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="绮铃kirin" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="洛逍遥" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="文文新闻" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="GM" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="小咪" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Fuxi Fuji" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="?独爱蝉HIA" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="没有咖啡的猫屎咖啡" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="雪风保佑我" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="zhnj" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="火苟" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="梦尽" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="阿泽free" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="阿乐" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="米线" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="依凡" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="涟燃556" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="夏季" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Game_ReAi" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="秦屎皇" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="6" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Zik yeung" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="awawa" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="智慧村村长" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="海中燕" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="就叫我好人吧" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="梦的回响" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="。。" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="墨子吉安" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="有风" />
-                        <TextBlock Width="240" Margin="0,1.5" Text="孙嘉昊まごかひろし" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="C" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Haio" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="陆光" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="chuveiro" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="无名氏34613" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="mr_kevin_x" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="来早点" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="烤肉真香" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Chr0n0s" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="七" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="H?O" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="瑞霖RLD" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="AAcfun" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="鼬嘁" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="名为冉冉的猫" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="hduqggd55379" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="唠叨的土豆" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Lonely_Grey" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="Vergilio" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="このdioた" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="没睡醒的肉啊" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="铃鸢" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="lvke" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="V-Merlin" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="美式加冰?" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="半岛弥音" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="clclFL" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="事与愿违" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="SamXU1322" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="OCEANI" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="shenpli" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="咸鱼嫌鱼咸" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="kirsnow" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="白庶" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="pengyihang" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="憨憨剑踏" />
-                        <TextBlock Width="120" Margin="0,1.5" Text="SinceCN" />
-                    </WrapPanel>
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnDonateOutput" Grid.Column="0" MinWidth="140" Text="生成解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" Visibility="Collapsed" />
-                        <local:MyButton x:Name="BtnDonateDonate" Grid.Column="0" MinWidth="140" Text="赞助作者" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" EventType="打开网页" EventData="https://afdian.com/a/LTCat" />
-                        <local:MyButton x:Name="BtnDonateCopy" Grid.Column="1" MinWidth="140" Text="复制识别码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                        <local:MyButton x:Name="BtnDonateInput" Grid.Column="2" MinWidth="140" Text="输入解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="法律信息" IsSwaped="True" CanSwap="True">
-                <StackPanel Margin="25,39,23,20">
-                    <TextBlock Text="隐私说明" TextWrapping="Wrap" Margin="0,6,0,4" FontWeight="Bold" />
-                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="本软件的 User Agent 中包含启动器的版本号,以统计各版本的用户数量。&#13;除此之外,本软件不会向开发者上传任何其他信息。" TextWrapping="Wrap" />
-                    <TextBlock Text="其他信息" TextWrapping="Wrap" Margin="0,12,0,4" FontWeight="Bold" />
-                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="Copyright © 龙腾猫跃 2016. All Rights Reserved.&#13;计算机软件著作权登记号:2020SR0875133&#13;违法违规行为举报投诉邮箱:[email protected]&#13;非 MINECRAFT 官方产品。未经 MOJANG 或 MICROSOFT 批准,也不与 MOJANG 或 MICROSOFT 关联。" TextWrapping="Wrap" />
-                    <StackPanel Orientation="Horizontal" Height="35" HorizontalAlignment="Left" Margin="0,12,0,0">
-                        <local:MyButton Text="用户协议与免责声明" Width="170" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" EventType="打开网页" EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW" />
-                        <local:MyButton Text="开源代码" Width="140" Padding="13,0" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL" />
-                    </StackPanel>
-                </StackPanel>
-            </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="许可与版权声明" IsSwaped="True" CanSwap="True">
-                <Grid Margin="25,42,23,4">
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="Auto" />
-                        <ColumnDefinition Width="20" />
-                        <ColumnDefinition Width="Auto" />
-                        <ColumnDefinition />
-                    </Grid.ColumnDefinitions>
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="60" />
-                        <RowDefinition Height="Auto" />
-                    </Grid.RowDefinitions>
-                    <TextBlock Grid.Row="0" Grid.Column="0" Text="Java Launch Wrapper" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2022 00ll00&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="1" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper" />
-                    <local:MyButton Grid.Row="1" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper?tab=MIT-1-ov-file#readme" />
-
-                    <TextBlock Grid.Row="2" Grid.Column="0" Text="Newtonsoft.Json" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2007 James Newton-King&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="3" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.newtonsoft.com/json" />
-                    <local:MyButton Grid.Row="3" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://opensource.org/licenses/MIT" />
-                    
-                    <TextBlock Grid.Row="4" Grid.Column="0" Text="NAudio" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015 Mark Heath ([email protected])&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="5" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio" />
-                    <local:MyButton Grid.Row="5" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left" 
-                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio/blob/master/license.txt" />
-                    
-                    <TextBlock Grid.Row="6" Grid.Column="0" Text="Ookii.Dialogs" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2009 Sven Groot (Ookii.org)" TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="7" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.ookii.org/Software/Dialogs/" />
-                    <local:MyButton Grid.Row="7" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/caioproiete/ookii-dialogs-wpf/blob/master/LICENSE" />
-                    
-                    <TextBlock Grid.Row="8" Grid.Column="0" Text="Iconmonstr" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2019 iconmonstr (Alexander Kahlkopf)" TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="9" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://iconmonstr.com/" />
-                    <local:MyButton Grid.Row="9" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://iconmonstr.com/license/" />
-                    
-                    <TextBlock Grid.Row="10" Grid.Column="0" Text="libwebp wrapper for .NET" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2012 Imazen&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="11" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net" />
-                    <local:MyButton Grid.Row="11" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net/blob/master/LICENSE.txt" />
-
-                    <TextBlock Grid.Row="12" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
-                    <TextBlock Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
-                    <local:MyButton Grid.Row="13" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
-                                    EventType="打开网页" EventData="https://www.iconfont.cn/" />
-
-                </Grid>
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>
+<local:MyPageRight
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+    mc:Ignorable="d" x:Class="PageOtherAbout"
+    PanScroll="{Binding ElementName=PanBack}" RenderOptions.BitmapScalingMode="LowQuality">
+    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
+        <StackPanel x:Name="PanMain" Margin="25,10" Grid.IsSharedSizeScope="True">
+            <local:MyCard Margin="0,15" Title="关于">
+                <Grid Margin="21,40,21,16">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="40" />
+                        <ColumnDefinition />
+                        <ColumnDefinition Width="150" />
+                    </Grid.ColumnDefinitions>
+                    <local:MyListItem IsHitTestVisible="False" Title="龙腾猫跃" Margin="-5,0,15,0" Info="Plain Craft Launcher 的作者!" Grid.Row="0" Grid.Column="1" />
+                    <local:MyListItem IsHitTestVisible="False" Title="Plain Craft Launcher 2" Margin="-5,0,15,0" Info="当前版本:%VERSION%(%VERSIONCODE%.%BRANCH%)" Grid.Row="1" Grid.Column="1" x:Name="ItemAboutPcl" />
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Logo.png" Grid.Row="1" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/LTCat.jpg" Grid.Row="0" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <local:MyButton Text="赞助作者" Grid.Column="2" Grid.Row="0" Height="35" EventType="打开网页" EventData="https://afdian.com/a/LTCat" />
+                    <local:MyButton Text="查看源代码" Grid.Column="2" Grid.Row="1" Height="35" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
+                </Grid>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="特别鸣谢">
+                <Grid Margin="21,40,21,16">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                        <RowDefinition Height="45" />
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="40" />
+                        <ColumnDefinition />
+                        <ColumnDefinition Width="150" />
+                    </Grid.ColumnDefinitions>
+                    <local:MyListItem Title="bangbang93" Margin="-5,0,15,0" IsHitTestVisible="False" Info="提供 BMCLAPI 镜像源和 Forge 安装工具,详见 https://bmclapi.bangbang93.com" Grid.Row="0" Grid.Column="1" />
+                    <local:MyButton x:Name="BtnAboutBmclapi" Text="赞助镜像源" Grid.Row="0" Grid.Column="2" Height="35" />
+                    <local:MyListItem Title="MC 百科" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Mod 名称的中文翻译和更多相关信息!" Grid.Row="1" Grid.Column="1" />
+                    <local:MyButton x:Name="BtnAboutWiki" Text="打开百科" Grid.Row="1" Grid.Column="2" Height="35" />
+                    <local:MyListItem Title="00ll00" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Java Launch Wrapper 和一些重要服务支持!" Grid.Row="2" Grid.Column="1" />
+                    <local:MyListItem Title="z0z0r4" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 MCIM 中国 Mod 下载镜像源和帮助库图床!" Grid.Row="3" Grid.Column="1" />
+                    <local:MyListItem Title="Patrick" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="设计并制作了 PCL 图标,让我从做图标的水深火热中得到了解脱……" Grid.Row="4" Grid.Column="1" />
+                    <local:MyListItem Title="Hao_Tian" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="在内测中找出了一大堆没人想得到的诡异 Bug,有非同寻常的 Bug 体质" Grid.Row="5" Grid.Column="1" />
+                    <local:MyListItem Title="Minecraft 中文论坛" Margin="-5,0,15,0" IsHitTestVisible="False" Info="虽然已经关站了,但感谢此前提供了 MCBBS 镜像源……" Grid.Row="6" Grid.Column="1" />
+                    <local:MyListItem Title="PCL 内群的各位" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="感谢内群的沙雕网友们这么久以来对我和 PCL 的支持与鼓励!" Grid.Row="7" Grid.Column="1" />
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/bangbang93.png" Grid.Row="0" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/wiki.png" Grid.Row="1" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/00ll00.png" Grid.Row="2" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/z0z0r4.png" Grid.Row="3" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Patrick.png" Grid.Row="4" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/Hao_Tian.jpg" Grid.Row="5" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/MCBBS.png" Grid.Row="6" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                    <Image Source="/Plain Craft Launcher 2;component/Images/Heads/PCL2.png" Grid.Row="7" Margin="3">
+                        <Image.Clip>
+                            <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
+                        </Image.Clip>
+                    </Image>
+                </Grid>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="赞助者">
+                <StackPanel Margin="25,40,10,20">
+                    <TextBlock HorizontalAlignment="Left" Text="以下是 2025 年 6 月的高级赞助者们!感谢各位对 PCL 和伟大的咕咕咕事业的支持!" Margin="0,0,25,5.5" TextTrimming="None" TextWrapping="WrapWithOverflow" />
+                    <WrapPanel Margin="0,0,0,10">
                        <TextBlock Width="120" Margin="0,1.5" Text="星月" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Nemo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="长歌当天" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="飒黎lIlIlI" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="hy" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="SaDhhJe" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="风铃子啊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柠娜" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="W丶北辰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="才KRE" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Yang_mistakeof" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="close eyes" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="顾顾" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="LTuuxj" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="花吃" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="FICazk" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="屑屑你" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="凉凉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="WDSJ" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Tohka" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="lz" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="哦嚯嚯嚯皮筋头" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="陈肖杰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="MC小满" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="残光" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="玖玖璃" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="水晶OTZ" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="伯克礼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="白猫海贼船" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zhang1232" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Never" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Fish_pomelo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="生风" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="富士山下" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小七" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="发呆的呆毛君" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="天黑时看月亮" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="夜枫" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="_章333_" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="孙杰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="wby" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="封尾==" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="你与空白皆无名" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="鱼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zzxniubi" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="炎燚火淼淼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="三巿" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="奈落" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="华佗" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="oran9e" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="赤冰雨" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Urizen" />
+                        <TextBlock Width="120" Margin="0,1.5" Text=":)" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="skyqyzm" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="橘子皮丶" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="额外awa" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="安上月" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="念城南" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="多低山丘陵" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="书童呀" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="sxy192425" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="李好啊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="MH_Nine" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="周先生" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="原" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="良田满穗" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柴" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="庭师" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="。,。" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="DHLH" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Xayj" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="绳网用户#37" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="LLLL" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="深绯红" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="奇伢七星" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Akapigo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Shink_guns" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Aqua 极&amp;坏." />
+                        <TextBlock Width="120" Margin="0,1.5" Text="大事化小蛋糕" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Nakiya233" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="花小妖" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="septebr" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一只马喽" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="下暴雨BWB" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="QAQ" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="sl土豆社区服主B友" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Wang" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="......" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ResTmoW" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="bochibochibo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="铸魂之灵" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柏柏柏" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Mart1s" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KuruM148" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KaiTouCrow" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="LIE-SHAN" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="慈悲方舟" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="于析" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="陌道" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="摇椅上的猫" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="五个麦卷" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Miraclepoi" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="XiaoLI" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="啊吧啊吧的肥宅" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="不娶雨婷不改名" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="oppo19" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="博麗靈夢" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="莹 of yours" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ChinaFZXPVP" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="城之内sama" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ENXI" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="steve_c418" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="洛长安" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="magic_muggle" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小猫游六喵" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="JY" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="雪豹起司可颂" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="wlsy" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="齐先生爱溜达" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="是小泠啊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="xioyiao" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="BunnyMiku" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="dadagga" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="无极之笔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="xiaoyu" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="王梓杰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="wjbjjd" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Candy_Mo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="lettmin_" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="七九·Alnasl" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="辉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="何为最优解" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="crazy丶天人" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="XsBlueSky" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="冬将軍" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="tiechui" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Liu2Shi2Pu2" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Yu_s" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="刘四妹" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="枝皙" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="CC" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Slana23" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="MMMy" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Venuse" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="microjie" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="Miner#Cetyleno~^" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="春江花月夜" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="落南子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="AL1Su-爱丽丝" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="我木有呆毛" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="乌羽" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="知性de含羞草" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="星の殇" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="IED LTAM" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="格罗夫斯基" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="梦启泉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="岁月无痕" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="????" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="超级脑壳" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="空梦" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="薛小欣" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="蛋黄豆角" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="芒果茶州" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="癫风院院长" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="三十一" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="美醉了" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="中科院在逃量子计算机" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="." />
+                        <TextBlock Width="120" Margin="0,1.5" Text="枫" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="jjj" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="clilick" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="我很快乐" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Phoenix tamer" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Lsp77" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="太阳神" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ikun0014" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="IncredibleYu" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="在下粉毛控" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="SuperCrazyK" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="阿柔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="HaKNa" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="胡十一" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="菲利絲" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="迷梦的羽" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="20018" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="波比科普员" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Xiao_JIA" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="龙腾我是你的头号粉丝" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="keer1563" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="百鬼まル" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="DPSYnt" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="七色彩羽" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="臭氧化碳" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="情来风" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柒月" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="汪汪队队长" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="迷路的猫" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ktyxlan_lan" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="琳琅039" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="黑色额度" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="sahaua" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="宦昭" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="镜刹" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="房石诶多" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="熊" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="NQ" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="我志永纯" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="漫城" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="憨憨的小草" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="泽涛" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="星海鹿樱" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="祭°" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="赛冷特呀" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="无妄之灾" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ZGW2008" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="没事多喝感冒灵" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一梦筱筱" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="twokness" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="hhy7171" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="子虚" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="猫羽" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="有氧丁真" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="dddr" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="天气热" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Emmanuel" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="夕尽暮染" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Bob_Marcus" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="初心无尘" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="朱利安" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="461863352" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="庭" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Xing_K" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="yzt" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="nuycn" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="鸡人" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="爱无界i" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Zik yeung" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="墨子吉安" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柠果籽" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="cai1" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小比登T^T" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="hb" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="caizzz" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="yyin" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="無言" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="修" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="改头换面" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="无敌百搭的款式" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="御坂1354号" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="木子" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="萤火焱攸" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="盼盼不是Panda" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="烤肉真香" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ZhuTan12" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="tom" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="洛竹" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="foob" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Garylu" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="阮绍轩" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="xin822" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="BAIgivele" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ljc" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="暖笑" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="焦糖流酥" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="绚丽的星河" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="魔导兔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="吾乃斗笔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="君殇" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="沙轻" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="邓布利多" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="红薯散养专业户" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="十言" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="vvvghjvvv" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="法斯" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="DASENSUN" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="HTS" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="握不住信新心" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="sycuuj133" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="天杉大将军" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="风见翎" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="江心Rain" />
+                    </WrapPanel>
+                    <Grid Height="35">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                        </Grid.ColumnDefinitions>
+                        <local:MyButton x:Name="BtnDonateOutput" Grid.Column="0" MinWidth="140" Text="生成解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" Visibility="Collapsed" />
+                        <local:MyButton x:Name="BtnDonateDonate" Grid.Column="0" MinWidth="140" Text="赞助作者" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" EventType="打开网页" EventData="https://afdian.com/a/LTCat" />
+                        <local:MyButton x:Name="BtnDonateCopy" Grid.Column="1" MinWidth="140" Text="复制识别码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
+                        <local:MyButton x:Name="BtnDonateInput" Grid.Column="2" MinWidth="140" Text="输入解锁码" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
+                    </Grid>
+                </StackPanel>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="法律信息" IsSwaped="True" CanSwap="True">
+                <StackPanel Margin="25,39,23,20">
+                    <TextBlock Text="隐私说明" TextWrapping="Wrap" Margin="0,6,0,4" FontWeight="Bold" />
+                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="本软件的 User Agent 中包含启动器的版本号,以统计各版本的用户数量。&#13;除此之外,本软件不会向开发者上传任何其他信息。" TextWrapping="Wrap" />
+                    <TextBlock Text="其他信息" TextWrapping="Wrap" Margin="0,12,0,4" FontWeight="Bold" />
+                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="Copyright © 龙腾猫跃 2016. All Rights Reserved.&#13;计算机软件著作权登记号:2020SR0875133&#13;违法违规行为举报投诉邮箱:[email protected]&#13;非 MINECRAFT 官方产品。未经 MOJANG 或 MICROSOFT 批准,也不与 MOJANG 或 MICROSOFT 关联。" TextWrapping="Wrap" />
+                    <StackPanel Orientation="Horizontal" Height="35" HorizontalAlignment="Left" Margin="0,12,0,0">
+                        <local:MyButton Text="用户协议与免责声明" Width="170" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" EventType="打开网页" EventData="https://shimo.im/docs/rGrd8pY8xWkt6ryW" />
+                        <local:MyButton Text="开源代码" Width="140" Padding="13,0" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
+                    </StackPanel>
+                </StackPanel>
+            </local:MyCard>
+            <local:MyCard Margin="0,0,0,15" Title="许可与版权声明" IsSwaped="True" CanSwap="True">
+                <Grid Margin="25,42,23,4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition Width="20" />
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition />
+                    </Grid.ColumnDefinitions>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="60" />
+                        <RowDefinition Height="Auto" />
+                    </Grid.RowDefinitions>
+                    <TextBlock Grid.Row="0" Grid.Column="0" Text="Java Launch Wrapper" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2022 00ll00&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="1" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper" />
+                    <local:MyButton Grid.Row="1" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/00ll00/java_launch_wrapper?tab=MIT-1-ov-file#readme" />
+
+                    <TextBlock Grid.Row="2" Grid.Column="0" Text="Newtonsoft.Json" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2007 James Newton-King&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="3" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://www.newtonsoft.com/json" />
+                    <local:MyButton Grid.Row="3" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://opensource.org/licenses/MIT" />
+                    
+                    <TextBlock Grid.Row="4" Grid.Column="0" Text="NAudio" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015 Mark Heath ([email protected])&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="5" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio" />
+                    <local:MyButton Grid.Row="5" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left" 
+                                    EventType="打开网页" EventData="https://github.com/naudio/NAudio/blob/master/license.txt" />
+                    
+                    <TextBlock Grid.Row="6" Grid.Column="0" Text="Ookii.Dialogs" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2009 Sven Groot (Ookii.org)" TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="7" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://www.ookii.org/Software/Dialogs/" />
+                    <local:MyButton Grid.Row="7" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/caioproiete/ookii-dialogs-wpf/blob/master/LICENSE" />
+                    
+                    <TextBlock Grid.Row="8" Grid.Column="0" Text="Iconmonstr" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2019 iconmonstr (Alexander Kahlkopf)" TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="9" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://iconmonstr.com/" />
+                    <local:MyButton Grid.Row="9" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://iconmonstr.com/license/" />
+                    
+                    <TextBlock Grid.Row="10" Grid.Column="0" Text="libwebp wrapper for .NET" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2012 Imazen&#xa;Licensed under the MIT License." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="11" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net" />
+                    <local:MyButton Grid.Row="11" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://github.com/imazen/libwebp-net/blob/master/LICENSE.txt" />
+
+                    <TextBlock Grid.Row="12" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
+                    <TextBlock Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
+                    <local:MyButton Grid.Row="13" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
+                                    EventType="打开网页" EventData="https://www.iconfont.cn/" />
+
+                </Grid>
+            </local:MyCard>
+        </StackPanel>
+    </local:MyScrollViewer>
+</local:MyPageRight>

+ 1 - 1
Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb

@@ -227,7 +227,7 @@
                         DeleteDirectory(Version.Path)
                         Hint("版本 " & Version.Name & " 已永久删除!", HintType.Finish)
                     Else
-                        FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                        FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
                         Hint("版本 " & Version.Name & " 已删除到回收站!", HintType.Finish)
                     End If
                 Case 2

+ 3 - 3
Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml

@@ -205,7 +205,7 @@
                                 <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                             </Grid.ColumnDefinitions>
                             <local:MyButton x:Name="BtnSkinSave" Text="保存皮肤" MinWidth="140" Padding="13,0" Margin="0,0,20,0" Grid.Row="1" />
-                            <local:MyButton x:Name="BtnSkinCache" Text="刷新缓存" MinWidth="140" Padding="13,0" Margin="0,0,20,0" ToolTip="刷新并重新下载 PCL 当前显示的皮肤" Grid.Row="1" Grid.Column="1" />
+                            <local:MyButton x:Name="BtnSkinCache" Text="刷新" MinWidth="140" Padding="13,0" Margin="0,0,20,0" ToolTip="刷新并重新下载 PCL 当前显示的皮肤" Grid.Row="1" Grid.Column="1" />
                         </Grid>
                     </Grid>
                     <Grid x:Name="PanSkinChange" Margin="0,14,0,2" Height="35" Visibility="Collapsed">
@@ -240,10 +240,10 @@
                                         ToolTip="还原" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
                                         Logo="M530 0c287 0 521 229 521 511s-233 511-521 511c-233 0-436-151-500-368a63 63 0 0 1 44-79 65 65 0 0 1 80 43c48 162 200 276 375 276 215 0 390-171 390-383s-174-383-390-383c-103 0-199 39-270 106l21-5a63 63 0 0 1 33 123l-157 42a65 65 0 0 1-90-42l-49-183a65 65 0 1 1 126-33l6 26A524 524 0 0 1 530 0z" LogoScale="0.9" />
                     </StackPanel>
-                    <local:MyTextBox x:Name="TextAdvanceJvm" Grid.ColumnSpan="2" Tag="LaunchAdvanceJvm" Grid.Column="1" ToolTip="启动 Minecraft 时使用的额外 JVM 参数,除非有确定把握,否则请不要修改" MaxLength="4000"
+                    <local:MyTextBox x:Name="TextAdvanceJvm" Grid.ColumnSpan="2" Tag="LaunchAdvanceJvm" Grid.Column="1" ToolTip="启动 Minecraft 时使用的额外 JVM 参数,除非有确定把握,否则请不要修改。&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。" MaxLength="4000"
                                  Height="100" AcceptsReturn="True" TextWrapping="Wrap" VerticalContentAlignment="Top" Padding="0,5" VerticalScrollBarVisibility="Auto" />
                     <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏参数尾部" Margin="0,0,25,0" />
-                    <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="LaunchAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如输入 --demo 则会以试玩模式启动游戏。" />
+                    <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="LaunchAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如,输入 --demo 则会以试玩模式启动游戏。&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。" />
                     <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,0" />
                     <local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="LaunchAdvanceRun" Grid.Column="1" HintText=""
                                  ToolTip="在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xa; · {path}:PCL 的 exe 文件所在的文件夹&#xa; · {minecraft}:.minecraft 文件夹&#xa; · {verpath}:版本文件夹(.minecraft\versions\版本名\)&#xa; · {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xa; · {java}:游戏运行时的 Java 文件夹&#xa;&#xa;除此之外,也支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {uuid}:玩家的 UUID&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号&#xa;&#xa;例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}options.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />

+ 1 - 1
Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml

@@ -198,7 +198,7 @@
                             <ColumnDefinition />
                             <ColumnDefinition />
                         </Grid.ColumnDefinitions>
-                        <local:MyCheckBox Grid.Column="0" Text="禁止在下载时复制已存在的文件" Height="22" x:Name="CheckDebugSkipCopy" Tag="SystemDebugSkipCopy" ToolTip="在下载时不直接复制已经存在的文件,而是重新下载每个文件。&#xa;只建议在测试下载速度时开启。" />
+                        <local:MyCheckBox Grid.Column="0" Text="禁止在下载时从其他文件夹复制文件" Height="22" x:Name="CheckDebugSkipCopy" Tag="SystemDebugSkipCopy" ToolTip="在下载时不直接复制其他 MC 文件夹中已经存在的相同文件,而是重新下载文件。&#xa;只建议在测试下载速度时开启。" />
                         <local:MyCheckBox Grid.Column="1" Text="调试模式" Height="22" x:Name="CheckDebugMode" Tag="SystemDebugMode" ToolTip="显示调试信息与更多错误信息。&#xa;这会导致启动器性能略有下降,若无特殊需要不建议开启。" />
                         <local:MyCheckBox Grid.Column="2" Text="添加延迟" Height="22" x:Name="CheckDebugDelay" Tag="SystemDebugDelay" ToolTip="在各个环节添加随机的延迟,拖慢加载速度,以测试部分功能是否正常运行。&#xa;这会严重影响启动器运行,若无特殊需要不建议开启。" />
                     </Grid>

+ 6 - 14
Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb

@@ -1,6 +1,4 @@
-Imports NAudio.Wave
-
-Public Class PageSetupUI
+Public Class PageSetupUI
 
     Public Shadows IsLoaded As Boolean = False
 
@@ -276,7 +274,7 @@ Public Class PageSetupUI
 
     '顶部栏
     Private Sub BtnLogoChange_Click(sender As Object, e As EventArgs) Handles BtnLogoChange.Click
-        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp;*.jpeg)|*.png;*.jpg;*.gif;*.webp;*.jpeg", "选择图片")
+        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.jpeg;*.gif;*.webp)|*.png;*.jpg;*.jpeg;*.gif;*.webp", "选择图片")
         If FileName = "" Then Return
         Try
             '拷贝文件
@@ -321,7 +319,7 @@ Refresh:
             Return
         End If
         '没有图片则要求选择
-        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片")
+        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpeg;*.jpg;*.gif;*.webp)|*.png;*.jpeg;*.jpg;*.gif;*.webp", "选择图片")
         If FileName = "" Then
             FrmMain.ImageTitleLogo.Source = Nothing
             e.Handled = True
@@ -359,15 +357,9 @@ Refresh:
             PanMusicVolume.Visibility = Visibility.Visible
             PanMusicDetail.Visibility = Visibility.Visible
             BtnMusicClear.Visibility = Visibility.Visible
-            Dim validMusicCount As Integer = 0
-            For Each File In EnumerateFiles(Path & "PCL\Musics\")
-                Try
-                    If {".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png"}.Contains(File.Extension.ToLower) Then Continue For
-                    validMusicCount += 1
-                Catch ex As Exception
-                End Try
-            Next
-            CardMusic.Title = "背景音乐(" & validMusicCount & " 首)"
+            CardMusic.Title = "背景音乐(" &
+                EnumerateFiles(Path & "PCL\Musics\").Count(Function(f) {".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png"}.Contains(f.Extension.ToLower)) &
+                " 首)"
         Else
             PanMusicVolume.Visibility = Visibility.Collapsed
             PanMusicDetail.Visibility = Visibility.Collapsed

+ 4 - 4
Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb

@@ -564,8 +564,8 @@ Public Class PageVersionExport
             Sub()
                 Try
                     Dim ModrinthHashes = Loader.Input.Select(Function(m) m.ModrinthHash)
-                    Dim ModrinthRaw = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files", "POST",
-                        $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject)
+                    Dim ModrinthRaw As JObject = DlModRequest("https://api.modrinth.com/v2/version_files", HttpMethod.Post,
+                        $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")
                     For Each ModFile In Loader.Input
                         '查找对应的文件
                         If Not ModrinthRaw.ContainsKey(ModFile.ModrinthHash) Then Continue For
@@ -589,8 +589,8 @@ Public Class PageVersionExport
                 Try
                     If ModrinthUploadMode Then Return 'Modrinth 上传模式下,不能从 CurseForge 获取信息
                     Dim CurseForgeHashes = Loader.Input.Select(Function(m) m.CurseForgeHash)
-                    Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", "POST",
-                        $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
+                    Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", HttpMethod.Post,
+                        $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches")
                     For Each ResultJson As JObject In CurseForgeRaw
                         If Not ResultJson.ContainsKey("file") Then Continue For
                         Dim File As JObject = ResultJson("file")

+ 11 - 5
Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb

@@ -381,8 +381,9 @@ Install:
     ''' 下载 Mod。
     ''' </summary>
     Private Sub BtnManageDownload_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageDownload.Click, BtnHintDownload.Click
-        PageComp.TargetVersion = PageVersionLeft.Version '将当前版本设置为筛选器
-        PageComp.TargetName = SearchBox.Text '将当前搜索框内容设置为筛选器
+        '预设置当前版本和搜索框内容
+        PageComp.TargetVersion = PageVersionLeft.Version
+        PageComp.TargetName = SearchBox.Text
         FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadMod)
     End Sub
 
@@ -429,6 +430,11 @@ Install:
         If FrmMain.PageRight IsNot Me Then Return
         If My.Computer.Keyboard.CtrlKeyDown AndAlso e.Key = Key.A Then ChangeAllSelected(True)
     End Sub
+    Private Sub SearchBox_PreviewKeyDown(sender As Object, e As KeyEventArgs) Handles SearchBox.PreviewKeyDown
+        'Ctrl + A 会被搜索框捕获,导致无法全选,所以在按下 Ctrl + A 时转移焦点以便捕获
+        If SearchBox.Text.Any Then Return
+        If My.Computer.Keyboard.CtrlKeyDown AndAlso e.Key = Key.A Then PanBack.Focus()
+    End Sub
 
 #End Region
 
@@ -637,14 +643,14 @@ Install:
                 Try
                     For Each Entry As McMod In ModList
                         If File.Exists(Entry.Path) Then
-                            My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                            My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
                         Else
                             Log($"[Mod] 未找到更新前的 Mod 文件,跳过对它的删除:{Entry.Path}", LogLevel.Debug)
                         End If
                     Next
                     For Each Entry As KeyValuePair(Of String, String) In FileCopyList
                         If File.Exists(Entry.Value) Then
-                            My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                            My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
                             Log($"[Mod] 更新后的 Mod 文件已存在,将会把它放入回收站:{Entry.Value}", LogLevel.Debug)
                         End If
                         If Directory.Exists(GetPathFromFullPath(Entry.Value)) Then
@@ -733,7 +739,7 @@ Install:
                     If IsShiftPressed Then
                         File.Delete(ModEntity.Path)
                     Else
-                        My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                        My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
                     End If
                 Catch ex As OperationCanceledException
                     Log(ex, "删除 Mod 被主动取消")

+ 25 - 33
Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb

@@ -116,12 +116,14 @@
             Dim TempName As String = NewName & "_temp"
             Dim TempPath As String = PathMcFolder & "versions\" & TempName & "\"
             Dim IsCaseChangedOnly As Boolean = NewName.ToLower = OldName.ToLower
-            '重新加载版本 Json 信息,避免 HMCL 项被合并
+            '重新读取版本 JSON 信息,避免 JsonObject 中已被合并的项被重新存储
             Dim JsonObject As JObject
             Try
-                JsonObject = GetJson(ReadFile(PageVersionLeft.Version.Path & PageVersionLeft.Version.Name & ".json"))
+                Dim JsonPath As String = PageVersionLeft.Version.GetJsonPath()
+                JsonObject = GetJson(ReadFile(JsonPath))
+                File.Delete(JsonPath)
             Catch ex As Exception
-                Log(ex, "重命名读取 json 时失败")
+                Log(ex, "在重命名读取 json 时失败")
                 JsonObject = PageVersionLeft.Version.JsonObject
             End Try
             '重命名主文件夹
@@ -130,32 +132,24 @@
             '清理 ini 缓存
             IniClearCache(PageVersionLeft.Version.PathIndie & "options.txt")
             IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini")
-            '重命名 JAR 文件、JSON 文件与 natives 文件夹
-            If Directory.Exists(NewPath & OldName & "-natives") Then
+            '重命名 jar 文件与 natives 文件夹
+            '不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443)
+            If Directory.Exists($"{NewPath}{OldName}-natives") Then
                 If IsCaseChangedOnly Then
-                    My.Computer.FileSystem.RenameDirectory(NewPath & OldName & "-natives", OldName & "natives" & "_temp")
-                    My.Computer.FileSystem.RenameDirectory(NewPath & OldName & "-natives" & "_temp", NewName & "-natives")
+                    My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{OldName}natives_temp")
+                    My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives_temp", $"{NewName}-natives")
                 Else
-                    DeleteDirectory(NewPath & NewName & "-natives")
-                    My.Computer.FileSystem.RenameDirectory(NewPath & OldName & "-natives", NewName & "-natives")
+                    DeleteDirectory($"{NewPath}{NewName}-natives")
+                    My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{NewName}-natives")
                 End If
             End If
-            If File.Exists(NewPath & OldName & ".jar") Then
+            If File.Exists($"{NewPath}{OldName}.jar") Then
                 If IsCaseChangedOnly Then
-                    My.Computer.FileSystem.RenameFile(NewPath & OldName & ".jar", OldName & "_temp" & ".jar")
-                    My.Computer.FileSystem.RenameFile(NewPath & OldName & "_temp" & ".jar", NewName & ".jar")
+                    My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{OldName}_temp.jar")
+                    My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}_temp.jar", $"{NewName}.jar")
                 Else
-                    File.Delete(NewPath & NewName & ".jar")
-                    My.Computer.FileSystem.RenameFile(NewPath & OldName & ".jar", NewName & ".jar")
-                End If
-            End If
-            If File.Exists(NewPath & OldName & ".json") Then
-                If IsCaseChangedOnly Then
-                    My.Computer.FileSystem.RenameFile(NewPath & OldName & ".json", OldName & "_temp" & ".json")
-                    My.Computer.FileSystem.RenameFile(NewPath & OldName & "_temp" & ".json", NewName & ".json")
-                Else
-                    File.Delete(NewPath & NewName & ".json")
-                    My.Computer.FileSystem.RenameFile(NewPath & OldName & ".json", NewName & ".json")
+                    File.Delete($"{NewPath}{NewName}.jar")
+                    My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{NewName}.jar")
                 End If
             End If
             '替换版本设置文件中的路径
@@ -166,15 +160,13 @@
             If ReadIni(PathMcFolder & "PCL.ini", "Version") = OldName Then
                 WriteIni(PathMcFolder & "PCL.ini", "Version", NewName)
             End If
-            '更改版本 Json
-            If File.Exists(NewPath & NewName & ".json") Then
-                Try
-                    JsonObject("id") = NewName
-                    WriteFile(NewPath & NewName & ".json", JsonObject.ToString)
-                Catch ex As Exception
-                    Log(ex, "重命名版本 json 失败")
-                End Try
-            End If
+            '写入版本 Json
+            Try
+                JsonObject("id") = NewName
+                WriteFile(NewPath & NewName & ".json", JsonObject.ToString)
+            Catch ex As Exception
+                Log(ex, "重命名版本 json 失败")
+            End Try
             '刷新与提示
             Hint("重命名成功!", HintType.Finish)
             PageVersionLeft.Version = New McVersion(NewName).Load()
@@ -192,7 +184,7 @@
         '选择 自定义 时修改图片
         Try
             If ComboDisplayLogo.SelectedItem Is ItemDisplayLogoCustom Then
-                Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.jpeg)|*.png;*.jpg;*.gif;*.jpeg", "选择图片")
+                Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpeg;*.jpg;*.gif;*.webp)|*.png;*.jpeg;*.jpg;*.gif;*.webp", "选择图片")
                 If FileName = "" Then
                     Reload() '还原选项
                     Return

+ 2 - 2
Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml

@@ -209,10 +209,10 @@
                         <RowDefinition Height="Auto" />
                     </Grid.RowDefinitions>
                     <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="JVM 参数头部" Margin="0,0,25,0" />
-                    <local:MyTextBox x:Name="TextAdvanceJvm" Grid.Row="0" Grid.ColumnSpan="2" Tag="VersionAdvanceJvm" Grid.Column="1" MaxLength="4000" ToolTip="启动 Minecraft 时使用的额外 JVM 参数,在没有确定把握的情况下请不要尝试修改。&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置"
+                    <local:MyTextBox x:Name="TextAdvanceJvm" Grid.Row="0" Grid.ColumnSpan="2" Tag="VersionAdvanceJvm" Grid.Column="1" MaxLength="4000" ToolTip="启动 Minecraft 时使用的额外 JVM 参数,在没有确定把握的情况下请不要尝试修改。&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置"
                                      Height="100" AcceptsReturn="True" TextWrapping="Wrap" VerticalContentAlignment="Top" Padding="0,5" VerticalScrollBarVisibility="Auto" />
                     <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏参数尾部" Margin="0,0,25,0" />
-                    <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如输入 --demo 则会以试玩模式启动游戏。&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置" />
+                    <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如,输入 --demo 则会以试玩模式启动游戏。&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置" />
                     <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,3" />
                     <local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionAdvanceRun" Grid.Column="1" HintText="" Margin="0,0,0,3"
                                      ToolTip="该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行版本设置的命令。&#xa;&#xa;在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xa; · {path}:PCL 的 exe 文件所在的文件夹&#xa; · {minecraft}:.minecraft 文件夹&#xa; · {verpath}:版本文件夹(.minecraft\versions\版本名\)&#xa; · {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xa; · {java}:游戏运行时的 Java 文件夹&#xa;&#xa;除此之外,也支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {uuid}:玩家的 UUID&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号&#xa;&#xa;例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}options.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />

+ 45 - 6
Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj

@@ -140,6 +140,21 @@
     <Prefer32Bit>false</Prefer32Bit>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="CacheCow.Client, Version=2.13.1.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>Resources\CacheCow.Client.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="CacheCow.Client.FileStore, Version=2.13.1.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>Resources\CacheCow.Client.FileStore.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="CacheCow.Common, Version=2.13.1.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>Resources\CacheCow.Common.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
     <Reference Include="Imazen.WebP, Version=10.0.1.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>Resources\Imazen.WebP.dll</HintPath>
@@ -149,8 +164,9 @@
       <SpecificVersion>False</SpecificVersion>
       <Private>False</Private>
     </Reference>
-    <Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
-      <HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
+    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>Resources\Newtonsoft.Json.dll</HintPath>
       <Private>False</Private>
     </Reference>
     <Reference Include="Ookii.Dialogs.Wpf, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0c15020868fd6249, processorArchitecture=MSIL">
@@ -158,13 +174,20 @@
       <HintPath>Resources\Ookii.Dialogs.Wpf.dll</HintPath>
       <Private>False</Private>
     </Reference>
+    <Reference Include="System.Configuration" />
     <Reference Include="System.Core" />
     <Reference Include="System.Drawing" />
     <Reference Include="System.IO.Compression" />
     <Reference Include="System.IO.Compression.FileSystem" />
     <Reference Include="System.Management" />
     <Reference Include="System.Net.Http" />
+    <Reference Include="System.Net.Http.Formatting, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>Resources\System.Net.Http.Formatting.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
     <Reference Include="System.Numerics" />
+    <Reference Include="System.Runtime.Caching" />
     <Reference Include="System.ServiceModel" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml.Linq" />
@@ -195,11 +218,14 @@
     <Compile Include="Controls\MyIconTextButton.xaml.vb">
       <DependentUpon>MyIconTextButton.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Modules\Base\ModDragHelper.vb" />
+
+
+
     <Compile Include="Modules\Minecraft\ModComp.vb" />
     <Compile Include="Modules\Minecraft\ModJava.vb" />
     <Compile Include="Modules\Minecraft\ModMod.vb" />
     <Compile Include="Modules\Minecraft\ModModpack.vb" />
+    <Compile Include="Modules\ThirdParty\DragHelper.vb" />
     <Compile Include="Pages\PageVersion\MyLocalModItem.xaml.vb">
       <DependentUpon>MyLocalModItem.xaml</DependentUpon>
     </Compile>
@@ -233,9 +259,6 @@
     <Compile Include="Modules\ModDevelop.vb" />
     <Compile Include="Modules\Minecraft\ModWatcher.vb" />
     <Compile Include="Modules\ModSecret.vb" />
-    <Compile Include="Modules\Reference\CookieWebClient.vb">
-      <SubType>Component</SubType>
-    </Compile>
     <Compile Include="Pages\PageDownload\Comp\PageComp.xaml.vb">
       <DependentUpon>PageComp.xaml</DependentUpon>
     </Compile>
@@ -499,6 +522,7 @@
       <DependentUpon>FormMain.xaml</DependentUpon>
       <SubType>Code</SubType>
     </Compile>
+
     <Page Include="Pages\PageVersion\MyLocalModItem.xaml">
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
@@ -721,6 +745,7 @@
     <Import Include="System.IO" />
     <Import Include="System.Linq" />
     <Import Include="System.Net" />
+    <Import Include="System.Net.Http" />
     <Import Include="System.Text" />
     <Import Include="System.Threading" />
     <Import Include="System.Xml.Linq" />
@@ -946,6 +971,7 @@
     <Resource Include="Images\Heads\MCBBS.png" />
   </ItemGroup>
   <ItemGroup>
+
   </ItemGroup>
   <ItemGroup>
     <Resource Include="Images\Blocks\NeoForge.png" />
@@ -957,5 +983,18 @@
     <None Include="Resources\Imazen.WebP.dll" />
     <None Include="Resources\libwebp64.dll" />
   </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\CacheCow.Client.FileStore.dll" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\CacheCow.Common.dll" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\CacheCow.Client.dll" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\System.Net.Http.Formatting.dll" />
+  </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
 </Project>

BIN
Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll


BIN
Plain Craft Launcher 2/Resources/CacheCow.Client.dll


BIN
Plain Craft Launcher 2/Resources/CacheCow.Common.dll


+ 4 - 0
Plain Craft Launcher 2/Resources/Custom.xaml

@@ -134,6 +134,10 @@
                     Text="切换到下载页面" EventType="切换页面" EventData="1|1"/>
         <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
                     Text="切换到启动器设置" EventType="切换页面" EventData="3|2"/>
+		<TextBlock Margin="0,4,0,4"
+                    Text="· 下载版本:切换到 EventData 对应的 Minecraft 下载页面"/>
+		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+                    Text="下载 Minecraft 1.12.2" EventType="下载版本" EventData="1.12.2"/>
     </StackPanel>
 </local:MyCard>
 

BIN
Plain Craft Launcher 2/Resources/Help.zip


File diff suppressed because it is too large
+ 117 - 117
Plain Craft Launcher 2/Resources/ModData.txt


BIN
Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll


BIN
Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll


BIN
Plain Craft Launcher 2/Resources/java-wrapper.jar


Some files were not shown because too many files changed in this diff