Просмотр исходного кода

Merge branch 'main' into fix/setup-binding

龙腾猫跃 1 год назад
Родитель
Сommit
45b6582f30
85 измененных файлов с 2207 добавлено и 1851 удалено
  1. 1 1
      .github/ISSUE_TEMPLATE/bug1.yml
  2. 1 1
      .github/ISSUE_TEMPLATE/bug2.yml
  3. 1 1
      .github/ISSUE_TEMPLATE/bug9.yml
  4. 2 2
      .github/ISSUE_TEMPLATE/ch.yml
  5. 3 3
      .github/ISSUE_TEMPLATE/config.yml
  6. 2 2
      .github/ISSUE_TEMPLATE/feature.yml
  7. 2 3
      .github/workflows/build.yml
  8. 1 1
      LICENCE
  9. 4 0
      Plain Craft Launcher 2/App.config
  10. 3 0
      Plain Craft Launcher 2/Application.xaml
  11. 21 44
      Plain Craft Launcher 2/Application.xaml.vb
  12. 187 0
      Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb
  13. 37 18
      Plain Craft Launcher 2/Controls/MyCard.vb
  14. 1 1
      Plain Craft Launcher 2/Controls/MyTextBox.vb
  15. 1 1
      Plain Craft Launcher 2/Controls/MyTextButton.vb
  16. 62 17
      Plain Craft Launcher 2/FormMain.xaml.vb
  17. 94 75
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  18. 1 1
      Plain Craft Launcher 2/Modules/Base/ModLoader.vb
  19. 411 622
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  20. 37 25
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  21. 28 11
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  22. 51 80
      Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
  23. 12 7
      Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
  24. 120 76
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  25. 111 88
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  26. 14 15
      Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
  27. 2 1
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  28. 2 2
      Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb
  29. 4 0
      Plain Craft Launcher 2/Modules/ModEvent.vb
  30. 1 1
      Plain Craft Launcher 2/Modules/ModMusic.vb
  31. 21 27
      Plain Craft Launcher 2/Modules/ModSecret.vb
  32. 0 59
      Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb
  33. 151 0
      Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb
  34. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  35. 53 13
      Plain Craft Launcher 2/My Project/Resources.Designer.vb
  36. 19 7
      Plain Craft Launcher 2/My Project/Resources.resx
  37. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb
  38. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml
  39. 8 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb
  40. 14 11
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb
  41. 30 27
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  42. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb
  43. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb
  44. 7 7
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
  45. 46 41
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
  46. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb
  47. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb
  48. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb
  49. 34 23
      Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
  50. 6 5
      Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
  51. 15 13
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
  52. 8 6
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
  53. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml
  54. 1 0
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
  55. 3 2
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml
  56. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
  57. 252 268
      Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
  58. 0 6
      Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb
  59. 1 1
      Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb
  60. 2 2
      Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb
  61. 3 0
      Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb
  62. 4 4
      Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
  63. 4 3
      Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb
  64. 7 7
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
  65. 19 13
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
  66. 5 3
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb
  67. 5 3
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
  68. 10 2
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml
  69. 4 4
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
  70. 11 4
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
  71. 28 29
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
  72. 7 5
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
  73. 12 1
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
  74. 41 5
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  75. BIN
      Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll
  76. BIN
      Plain Craft Launcher 2/Resources/CacheCow.Client.dll
  77. BIN
      Plain Craft Launcher 2/Resources/CacheCow.Common.dll
  78. 28 20
      Plain Craft Launcher 2/Resources/Custom.xaml
  79. BIN
      Plain Craft Launcher 2/Resources/Help.zip
  80. 117 117
      Plain Craft Launcher 2/Resources/ModData.txt
  81. BIN
      Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll
  82. BIN
      Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll
  83. BIN
      Plain Craft Launcher 2/Resources/java-wrapper.jar
  84. 3 3
      README.md
  85. BIN
      最新正式版.zip

+ 1 - 1
.github/ISSUE_TEMPLATE/bug1.yml

@@ -12,7 +12,7 @@ body:
       required: false
     - label: "**我已尝试使用 HMCL 启动,HMCL 没有出现问题。** 如果 HMCL 也无法启动就不是 PCL 导致的问题,请 **不要** 提交反馈。[下载 HMCL](https://hmcl.huangyuhui.net/download)"
       required: true
-    - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
+    - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
       required: true
 - type: textarea
   id: "yml-2"

+ 1 - 1
.github/ISSUE_TEMPLATE/bug2.yml

@@ -12,7 +12,7 @@ body:
       required: false
     - label: "我知晓大多数此类问题都是网络环境不佳导致的,但我确实认为我的问题可能是 PCL 导致的,和网络环境无关。"
       required: true
-    - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
+    - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
       required: true
 - type: textarea
   id: "yml-2"

+ 1 - 1
.github/ISSUE_TEMPLATE/bug9.yml

@@ -8,7 +8,7 @@ body:
     label: "检查项"
     description: "请逐个检查下列项目,并勾选确认。"
     options:
-    - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
+    - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
       required: true
 - type: textarea
   id: "yml-2"

+ 2 - 2
.github/ISSUE_TEMPLATE/ch.yml

@@ -8,9 +8,9 @@ body:
     label: "检查项"
     description: "请逐个检查下列项目,并勾选确认。"
     options:
-    - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一建议未被提交过。"
+    - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一建议未被提交过。"
       required: true
-    - label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。"
+    - label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。"
       required: true
 - type: textarea
   id: "yml-2"

+ 3 - 3
.github/ISSUE_TEMPLATE/config.yml

@@ -1,14 +1,14 @@
 blank_issues_enabled: false
 contact_links:
   - name: 主页预设反馈
-    url: https://github.com/Hex-Dragon/PCL2/discussions/categories/自定义主页
+    url: https://github.com/Meloong-Git/PCL/discussions/categories/自定义主页
     about: 提交与预设的主页(设置 → 个性化 → 主页预设)中的具体内容相关的反馈
   - name: 帮助文档反馈
     url: https://github.com/LTCatt/PCL2Help/issues
     about: 提交与 PCL 帮助文档(更多 → 帮助)中的具体内容相关的反馈
   - name: 提问
-    url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E6%8F%90%E9%97%AE
+    url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE
     about: 我想问一些 PCL 相关的问题……
   - name: 讨论
-    url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E8%AE%A8%E8%AE%BA
+    url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E8%AE%A8%E8%AE%BA
     about: 我想讨论一些 PCL 相关的事情……

+ 2 - 2
.github/ISSUE_TEMPLATE/feature.yml

@@ -8,9 +8,9 @@ body:
     label: "检查项"
     description: "请逐个检查下列项目,并勾选确认。"
     options:
-    - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一提案未被提交过。"
+    - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一提案未被提交过。"
       required: true
-    - label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。"
+    - label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。"
       required: true
     - label: "我知晓还没做的新功能真的太多了,忙不过来,所以新功能提案几乎不会被处理,也不建议再提交新功能提案 qwq……"
       required: true

+ 2 - 3
.github/workflows/build.yml

@@ -38,7 +38,6 @@ jobs:
     
     - name: Update Help
       run:  |
-        $workpath = [System.Environment]::CurrentDirectory
         Set-Location ..
         git clone https://github.com/LTCatt/PCL2Help.git -b master --single-branch --depth 1
         Set-Location PCL2Help
@@ -52,8 +51,8 @@ jobs:
 
     - name: Replace
       run: |
-        (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public Const OAuthClientId As String = ""', 'Public Const OAuthClientId As String = "${{ secrets.CLIENT_ID }}"' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
-        (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public Const CurseForgeAPIKey As String = ""', 'Public Const CurseForgeAPIKey As String = "${{ secrets.CURSEFORGE_API_KEY }}"' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
+        (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "")', 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "${{ secrets.CLIENT_ID }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
+        (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "")', 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "${{ secrets.CURSEFORGE_API_KEY }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
         (gc "Plain Craft Launcher 2\Modules\Base\ModBase.vb") -replace 'Public Const CommitHash As String = ""', 'Public Const CommitHash As String = "${{ github.sha }}"' | Out-File "Plain Craft Launcher 2\Modules\Base\ModBase.vb"
         
     - name: Build

+ 1 - 1
LICENCE

@@ -44,6 +44,6 @@
 这些规则主要是为了阻止恶意的使用和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧!
 
 如果你只是参考了一小段代码,署个名就行啦,不用担心。
-如果对具体细节有疑问,欢迎在 https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E6%8F%90%E9%97%AE 发帖询问!
+如果对具体细节有疑问,欢迎在 https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE 发帖询问!
 
 多谢大家啦!

+ 4 - 0
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>

+ 3 - 0
Plain Craft Launcher 2/Application.xaml

@@ -1,6 +1,7 @@
 <Application x:Class="Application"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:behaviors="clr-namespace:PCL.Controls.Behaviors"
     xmlns:local="clr-namespace:PCL"
     xmlns:sys="clr-namespace:System;assembly=mscorlib"
     StartupUri="FormMain.xaml"
@@ -302,6 +303,7 @@
                         </ControlTemplate>
                     </Setter.Value>
                 </Setter>
+                <Setter Property="behaviors:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
             </Style>
             <Style TargetType="PasswordBox">
                 <Setter Property="FontSize" Value="13" />
@@ -343,6 +345,7 @@
                         </ControlTemplate>
                     </Setter.Value>
                 </Setter>
+                <Setter Property="behaviors:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
             </Style>
 
             <!-- ComboBox -->

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

@@ -140,6 +140,7 @@ WaitRetry:
             ServicePointManager.Expect100Continue = True
             ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
             ServicePointManager.DefaultConnectionLimit = 1024
+            ServicePointManager.UseNagleAlgorithm = False
             '计时
             Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
             ApplicationStartTick = GetTimeTick()
@@ -181,53 +182,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
 
     '切换窗口

+ 187 - 0
Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb

@@ -0,0 +1,187 @@
+' Author: uye (owner of the MaaAssistantArknights team)
+' Original Source: MaaAssistantArknights project - https://github.com/MaaAssistantArknights/MaaAssistantArknights
+' License: Apache License 2.0 (this file only)
+'
+' This file is originally developed for MaaAssistantArknights and licensed under AGPL v3.0.
+' As the original author and copyright holder,
+' I hereby re-license this file under the Apache License 2.0 for use in the Plain Craft Launcher project.
+'
+' This file is contributed under the spirit of good faith and open cooperation.
+' It does not implement any core launcher functions of PCL.
+'
+' Description:
+' Provides a WPF clipboard handling fix to avoid OpenClipboard exceptions
+' in TextBox, RichTextBox, and DataGrid under Windows focus issues or clipboard hooks.
+'
+' Date: 2025-07-03
+
+Imports System.Windows
+Imports System.Windows.Controls
+Imports System.Windows.Input
+Imports System.Windows.Documents
+Imports System.Linq
+
+Namespace Controls.Behaviors
+    Public NotInheritable Class ClipboardInterceptor
+        Private Sub New()
+        End Sub
+
+        Public Shared ReadOnly EnableSafeClipboardProperty As DependencyProperty =
+            DependencyProperty.RegisterAttached("EnableSafeClipboard", GetType(Boolean), GetType(ClipboardInterceptor),
+                                                New PropertyMetadata(False, AddressOf OnEnableSafeClipboardChanged))
+
+        Public Shared Sub SetEnableSafeClipboard(element As DependencyObject, value As Boolean)
+            element.SetValue(EnableSafeClipboardProperty, value)
+        End Sub
+
+        Public Shared Function GetEnableSafeClipboard(element As DependencyObject) As Boolean
+            Return CBool(element.GetValue(EnableSafeClipboardProperty))
+        End Function
+
+        Private Shared Sub OnEnableSafeClipboardChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
+            If TypeOf d Is TextBox AndAlso CBool(e.NewValue) Then
+                AddCommandBindingsToTextBox(DirectCast(d, TextBox))
+            ElseIf TypeOf d Is RichTextBox AndAlso CBool(e.NewValue) Then
+                AddCommandBindingsToRichTextBox(DirectCast(d, RichTextBox))
+            ElseIf TypeOf d Is DataGrid AndAlso CBool(e.NewValue) Then
+                AddCommandBindingsToDataGrid(DirectCast(d, DataGrid))
+            End If
+        End Sub
+
+        Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox)
+            tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox))
+            tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox))
+            tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox))
+        End Sub
+
+        Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox)
+            rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyRichTextBox))
+            rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutRichTextBox))
+            rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteRichTextBox))
+        End Sub
+
+        Private Shared Sub AddCommandBindingsToDataGrid(dg As DataGrid)
+            dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid))
+        End Sub
+
+        Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs)
+            Dim tb = TryCast(sender, TextBox)
+            If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
+
+            Try
+                Forms.Clipboard.Clear()
+                Forms.Clipboard.SetDataObject(tb.SelectedText, True)
+            Catch
+            End Try
+
+            e.Handled = True
+        End Sub
+
+        Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs)
+            Dim tb = TryCast(sender, TextBox)
+            If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
+
+            Try
+                Forms.Clipboard.Clear()
+                Forms.Clipboard.SetDataObject(tb.SelectedText, True)
+            Catch
+            End Try
+
+            tb.SelectedText = String.Empty
+            e.Handled = True
+        End Sub
+
+        Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs)
+            Dim tb = TryCast(sender, TextBox)
+            If tb Is Nothing Then Return
+
+            If Forms.Clipboard.ContainsText() Then
+                Dim pasteText = Forms.Clipboard.GetText()
+                Dim start = tb.SelectionStart
+
+                tb.SelectedText = pasteText
+                tb.CaretIndex = start + pasteText.Length
+                tb.SelectionLength = 0
+            End If
+
+            e.Handled = True
+        End Sub
+
+        Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
+            Dim rtb = TryCast(sender, RichTextBox)
+            If rtb Is Nothing Then Return
+
+            Dim textRange = New TextRange(rtb.Selection.Start, rtb.Selection.End)
+            If String.IsNullOrEmpty(textRange.Text) Then Return
+
+            Try
+                Forms.Clipboard.Clear()
+                Forms.Clipboard.SetDataObject(textRange.Text, True)
+            Catch
+            End Try
+
+            e.Handled = True
+        End Sub
+
+        Private Shared Sub OnCutRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
+            Dim rtb = TryCast(sender, RichTextBox)
+            If rtb Is Nothing Then Return
+
+            Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End)
+            If String.IsNullOrEmpty(selection.Text) Then Return
+
+            Try
+                Forms.Clipboard.Clear()
+                Forms.Clipboard.SetDataObject(selection.Text, True)
+            Catch
+            End Try
+
+            selection.Text = String.Empty
+            e.Handled = True
+        End Sub
+
+        Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
+            Dim rtb = TryCast(sender, RichTextBox)
+            If rtb Is Nothing Then Return
+
+            If Not Forms.Clipboard.ContainsText() Then Return
+
+            Dim pasteText = Forms.Clipboard.GetText()
+            Dim selection = rtb.Selection
+
+            selection.Text = pasteText
+
+            Dim caretPos = selection.End
+            rtb.CaretPosition = caretPos
+            rtb.Selection.Select(caretPos, caretPos)
+
+            e.Handled = True
+        End Sub
+
+        Private Shared Sub OnCopyDataGrid(sender As Object, e As ExecutedRoutedEventArgs)
+            Dim dg = TryCast(sender, DataGrid)
+            If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return
+
+            Dim sb = New System.Text.StringBuilder()
+            Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item)
+
+            For Each row In rowGroups
+                Dim rowText = String.Join(vbTab, row.Select(Function(cell)
+                                                                Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock)
+                                                                Return If(tb IsNot Nothing, tb.Text, "")
+                                                            End Function))
+                sb.AppendLine(rowText)
+            Next
+
+            Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf)
+
+            Try
+                Forms.Clipboard.Clear()
+                Forms.Clipboard.SetDataObject(sbStr, True)
+            Catch
+            End Try
+
+            e.Handled = True
+        End Sub
+    End Class
+End Namespace

+ 37 - 18
Plain Craft Launcher 2/Controls/MyCard.vb

@@ -90,7 +90,7 @@
             MainGrid.Children.Add(MainSwap)
         End If
         '改变默认的折叠
-        If IsSwaped AndAlso SwapControl IsNot Nothing Then
+        If IsSwapped AndAlso SwapControl IsNot Nothing Then
             MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0))
             '取消由于高度变化被迫触发的高度动画
             Dim RawUseAnimation As Boolean = UseAnimation
@@ -224,7 +224,7 @@
     Private ActualUsedHeight As Double '回滚实际高度(例如 NaN)
     Private Sub MySizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged
         If Not UseAnimation Then Return
-        Dim DeltaHeight As Double = If(IsSwaped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height
+        Dim DeltaHeight As Double = If(IsSwapped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height
         '卡片的进入时动画已被页面通用切换动画替代
         If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Return
         StartHeightAnimation(DeltaHeight, e.PreviousSize.Height, False)
@@ -269,18 +269,18 @@
         Sub()
             IsHeightAnimating = False
             Height = ActualUsedHeight
-            If IsSwaped AndAlso SwapControl IsNot Nothing Then SwapControl.Visibility = Visibility.Collapsed
+            If IsSwapped AndAlso SwapControl IsNot Nothing Then SwapControl.Visibility = Visibility.Collapsed
         End Sub,, True))
         AniStart(AnimList, "MyCard Height " & Uuid)
         IsHeightAnimating = True
-        ActualUsedHeight = If(IsSwaped, SwapedHeight, Height)
+        ActualUsedHeight = If(IsSwapped, SwapedHeight, Height)
         Height = PreviousHeight
     End Sub
     ''' <summary>
     ''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。
     ''' </summary>
     Public Sub TriggerForceResize()
-        Height = If(IsSwaped, SwapedHeight, Double.NaN)
+        Height = If(IsSwapped, SwapedHeight, Double.NaN)
         AniStop("MyCard Height " & Uuid)
         IsHeightAnimating = False
     End Sub
@@ -300,26 +300,45 @@
     ''' <summary>
     ''' 是否已被折叠。
     ''' </summary>
-    Public Property IsSwaped As Boolean
+    Public Property IsSwapped As Boolean
         Get
-            Return _IsSwaped
+            Return _IsSwapped
         End Get
         Set(value As Boolean)
-            If _IsSwaped = value Then Return
-            _IsSwaped = value
+            If _IsSwapped = value Then Return
+            _IsSwapped = value
             If SwapControl Is Nothing Then Return
             '展开
-            If Not IsSwaped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title)
+            If Not IsSwapped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title)
             '若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行
             If Not IsLoaded Then Return
             '更新高度
             SwapControl.Visibility = Visibility.Visible
             TriggerForceResize()
             '改变箭头
-            AniStart(AaRotateTransform(MainSwap, If(_IsSwaped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True)
+            AniStart(AaRotateTransform(MainSwap, If(_IsSwapped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True)
         End Set
     End Property
-    Private _IsSwaped As Boolean = False
+    Private _IsSwapped As Boolean = False
+    ''' <summary>
+    ''' 是否已被折叠。(已过时,请使用 IsSwapped)
+    ''' </summary>
+    <Obsolete("IsSwaped 存在拼写错误,请换用 IsSwapped 属性。")>
+    Public Property IsSwaped As Boolean
+        Get
+            Return GetValue(IsSwapedProperty)
+        End Get
+        Set(value As Boolean)
+            SetValue(IsSwapedProperty, value)
+        End Set
+    End Property
+    Public Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False, AddressOf OnIsSwapedPropertyChanged))
+    Private Shared Sub OnIsSwapedPropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
+        Dim self = TryCast(d, MyCard)
+        If self IsNot Nothing Then
+            self.IsSwapped = CType(e.NewValue, Boolean)
+        End If
+    End Sub
 
     Public Property SwapLogoRight As Boolean = False
     Private IsMouseDown As Boolean = False
@@ -328,8 +347,8 @@
     Public Const SwapedHeight As Integer = 40
     Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
         Dim Pos As Double = Mouse.GetPosition(Me).Y
-        If Not IsSwaped AndAlso
-            (SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
+        If Not IsSwapped AndAlso
+            (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
         IsMouseDown = True
     End Sub
     Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
@@ -337,8 +356,8 @@
         IsMouseDown = False
 
         Dim Pos As Double = Mouse.GetPosition(Me).Y
-        If Not IsSwaped AndAlso
-            (SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
+        If Not IsSwapped AndAlso
+            (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
 
         Dim ee = New RouteEventArgs(True)
         RaiseEvent PreviewSwap(Me, ee)
@@ -347,8 +366,8 @@
             Return
         End If
 
-        IsSwaped = Not IsSwaped
-        Log("[Control] " & If(IsSwaped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title))
+        IsSwapped = Not IsSwapped
+        Log("[Control] " & If(IsSwapped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title))
         RaiseEvent Swap(Me, ee)
     End Sub
     Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave

+ 1 - 1
Plain Craft Launcher 2/Controls/MyTextBox.vb

@@ -264,7 +264,7 @@
             If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
                 '有动画
                 AniStart({
-                         AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
+                         AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
                          AaColor(Me, BackgroundProperty, BackColorName, AnimationTime)
                      }, "MyTextBox Color " & Uuid)
             Else

+ 1 - 1
Plain Craft Launcher 2/Controls/MyTextButton.vb

@@ -57,7 +57,7 @@
     Private Const AnimationTimeIn As Integer = 100
     Private Const AnimationTimeOut As Integer = 200
     Private ColorName As String
-    Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
+    Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
         '判断当前颜色
         Dim ForeName As String
         Dim Time As Integer

+ 62 - 17
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -1,4 +1,5 @@
 Imports System.ComponentModel
+Imports System.Windows.Interop
 
 Public Class FormMain
 
@@ -10,6 +11,19 @@ Public Class FormMain
         Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
         '统计更新日志条目
 #If BETA Then
+        If LastVersion < 367 Then 'Release 2.10.6
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:启用 MCIM 社区资源镜像源,以缓解社区资源难以下载的问题"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:正版登录出错时无法给出正确的错误信息"))
+            FeatureCount += 9
+            BugCount += 9
+        End If
+        If LastVersion < 365 Then 'Release 2.10.5
+            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 < 361 Then 'Release 2.10.3
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法安装部分使用老版本 PCL 导出的整合包"))
         End If
@@ -82,6 +96,25 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '1:BUG-
+        If LastVersion < 366 Then 'Snapshot 2.10.6
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:启用 MCIM 社区资源镜像源,以缓解社区资源难以下载的问题"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:正版登录出错时无法给出正确的错误信息"))
+            FeatureCount += 9
+            BugCount += 9
+        End If
+        If LastVersion < 364 Then 'Snapshot 2.10.5
+            If LastVersion >= 363 Then
+                FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法添加新的正版账号"))
+                BugCount += 1
+            End If
+        End If
+        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
@@ -225,7 +258,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
@@ -242,13 +275,18 @@ Public Class FormMain
         '加载 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
+            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 Closing, Sub() Helper.RemoveDragHook()
+            AddHandler Helper.DragDrop, Sub() FileDrag(Helper.DropFilePaths)
+        End If
         '切换到首页
         If Not IsNothing(FrmLaunchLeft.Parent) Then FrmLaunchLeft.SetValue(ContentPresenter.ContentProperty, Nothing)
         If Not IsNothing(FrmLaunchRight.Parent) Then FrmLaunchRight.SetValue(ContentPresenter.ContentProperty, Nothing)
@@ -365,7 +403,9 @@ Public Class FormMain
             Catch ex As Exception
                 Log(ex, "清理自动更新文件失败")
             End Try
-        End Sub, "Start Loader", ThreadPriority.Lowest)
+            '上报
+            Telemetry("启动")
+        End Sub, "初始化", ThreadPriority.Lowest)
 
         Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
     End Sub
@@ -540,7 +580,7 @@ Public Class FormMain
         If ReturnCode = ProcessReturnValues.Exception Then
             If Not IsLogShown Then
                 FeedbackInfo()
-                Log("请在 https://github.com/Hex-Dragon/PCL2/issues 提交错误报告,以便于作者解决此问题!")
+                Log("请在 https://github.com/Meloong-Git/PCL/issues 提交错误报告,以便于作者解决此问题!")
                 IsLogShown = True
                 ShellOnly(Path & "PCL\Log1.txt")
             End If
@@ -591,11 +631,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
@@ -605,9 +646,14 @@ Public Class FormMain
                 Else
                     Msg.Btn1_Click()
                 End If
-                Return
             End If
+            Return
         End If
+
+        '==========================
+        ' 在没有弹窗时:继续检查……
+        '==========================
+
         '按 ESC 返回上一级
         If e.Key = Key.Escape Then TriggerPageBack()
         '更改隐藏版本可见性
@@ -641,8 +687,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
         '鼠标侧键返回上一级
@@ -707,6 +751,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
@@ -845,7 +890,7 @@ Public Class FormMain
             '错误报告分析
             Try
                 Log("[System] 尝试进行错误报告分析")
-                Dim Analyzer As New CrashAnalyzer(GetUuid())
+                Dim Analyzer As New CrashAnalyzer
                 Analyzer.Import(FilePath)
                 If Not Analyzer.Prepare() Then Exit Try
                 Analyzer.Analyze()

+ 94 - 75
Plain Craft Launcher 2/Modules/Base/ModBase.vb

@@ -1,6 +1,6 @@
 Imports System.Globalization
 Imports System.IO.Compression
-Imports System.Reflection
+Imports System.Net.Sockets
 Imports System.Runtime.CompilerServices
 Imports System.Security.Cryptography
 Imports System.Security.Principal
@@ -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.6" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.10.6." & VersionBranchCode '标准格式的四段式版本号
     Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
 #If BETA Then
-    Public Const VersionCode As Integer = 361 'Release
+    Public Const VersionCode As Integer = 367 'Release
 #Else
-    Public Const VersionCode As Integer = 362 'Snapshot
+    Public Const VersionCode As Integer = 366 'Snapshot
 #End If
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -791,8 +791,8 @@ Public Module ModBase
     Public Sub CopyFile(FromPath As String, ToPath As String)
         Try
             '还原文件路径
-            If Not FromPath.Contains(":\") Then FromPath = Path & FromPath
-            If Not ToPath.Contains(":\") Then ToPath = Path & ToPath
+            FromPath = ShortenPath(If(FromPath.Contains(":\"), FromPath, Path & FromPath))
+            ToPath = ShortenPath(If(ToPath.Contains(":\"), ToPath, Path & ToPath))
             '如果复制同一个文件则跳过
             If FromPath = ToPath Then Return
             '确保目录存在
@@ -861,7 +861,7 @@ Public Module ModBase
     ''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
     Public Sub WriteFile(FilePath As String, Text As String, Optional Append As Boolean = False, Optional Encoding As Encoding = Nothing)
         '处理相对路径
-        If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
+        FilePath = ShortenPath(If(FilePath.Contains(":\"), FilePath, Path & FilePath))
         '确保目录存在
         Directory.CreateDirectory(GetPathFromFullPath(FilePath))
         '写入文件
@@ -886,7 +886,7 @@ Public Module ModBase
     ''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
     Public Sub WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False)
         '处理相对路径
-        If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
+        FilePath = ShortenPath(If(FilePath.Contains(":\"), FilePath, Path & FilePath))
         '确保目录存在
         Directory.CreateDirectory(GetPathFromFullPath(FilePath))
         '写入文件
@@ -1294,11 +1294,11 @@ Re:
     ''' </summary>
     Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing,
                            Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
-        Directory.CreateDirectory(DestDirectory)
+        Directory.CreateDirectory(ShortenPath(DestDirectory))
         If CompressFilePath.EndsWithF(".gz", True) Then
             '以 gz 方式解压
             Dim stream As New GZipStream(New FileStream(CompressFilePath, FileMode.Open, FileAccess.ReadWrite), CompressionMode.Decompress)
-            Dim decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", ""), FileMode.OpenOrCreate, FileAccess.Write)
+            Dim decompressedFile As New FileStream(ShortenPath(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", "")), FileMode.OpenOrCreate, FileAccess.Write)
             Dim data As Integer = stream.ReadByte()
             While data <> -1
                 decompressedFile.WriteByte(data)
@@ -1309,10 +1309,10 @@ Re:
         Else
             '以 zip 方式解压
             Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030")))
-                Dim TotalCount As Integer = Archive.Entries.Count
+                Dim TotalCount As Long = Archive.Entries.Count
                 For Each Entry As ZipArchiveEntry In Archive.Entries
-                    If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / TotalCount)
-                    Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
+                    If ProgressIncrementHandler IsNot Nothing AndAlso TotalCount > 0 Then ProgressIncrementHandler(1 / TotalCount)
+                    Dim DestinationPath As String = ShortenPath(IO.Path.Combine(DestDirectory, Entry.FullName))
                     If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then
                         Continue For '不创建空文件夹
                     Else
@@ -1387,10 +1387,10 @@ RetryDir:
         ToPath = ToPath.Replace("/", "\")
         If Not ToPath.EndsWithF("\") Then ToPath &= "\"
         Dim AllFiles = EnumerateFiles(FromPath).ToList
-        Dim FileCount As Integer = AllFiles.Count
+        Dim FileCount As Long = AllFiles.Count
         For Each File In AllFiles
             CopyFile(File.FullName, File.FullName.Replace(FromPath, ToPath))
-            If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / FileCount)
+            If ProgressIncrementHandler IsNot Nothing AndAlso FileCount > 0 Then ProgressIncrementHandler(1 / FileCount)
         Next
     End Sub
     ''' <summary>
@@ -1449,22 +1449,8 @@ RetryDir:
             IsInner = True
         Loop
 
-        '常见错误(记得同时修改下面的)
-        Dim CommonReason As String = Nothing
-        If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
-            CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。"
-        ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
-            CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
-        ElseIf TypeOf InnerEx Is OutOfMemoryException Then
-            CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。"
-        ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
-            CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
-        ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
-                "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) DescList.Any(Function(l) l.Contains(s))) Then
-            CommonReason = "你的网络环境不佳,导致难以连接到服务器。请稍后重试,或使用 VPN 以改善网络环境。"
-        End If
-
         '构造输出信息
+        Dim CommonReason As String = AnalyzeCommonReason(InnerEx, DescList)
         If CommonReason Is Nothing Then
             Return DescList.Join(vbCrLf)
         Else
@@ -1492,22 +1478,8 @@ RetryDir:
         DescList = DescList.Distinct.ToList
         Dim Desc As String = Join(DescList, vbCrLf & "→ ")
 
-        '常见错误(记得同时修改上面的)
-        Dim CommonReason As String = Nothing
-        If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
-            CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。"
-        ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
-            CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
-        ElseIf TypeOf InnerEx Is OutOfMemoryException Then
-            CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。"
-        ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
-            CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
-        ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
-                "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) Desc.Contains(s)) Then
-            CommonReason = "你的网络环境不佳,导致难以连接到服务器。请稍后重试,或使用 VPN 以改善网络环境。"
-        End If
-
         '构造输出信息
+        Dim CommonReason As String = AnalyzeCommonReason(InnerEx, DescList)
         If CommonReason IsNot Nothing Then
             Return CommonReason & "详细错误:" & DescList.First
         Else
@@ -1515,6 +1487,24 @@ RetryDir:
             Return Join(DescList, " ← ")
         End If
     End Function
+    Private Function AnalyzeCommonReason(InnerEx As Exception, DescList As List(Of String)) As String
+        If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
+            Return "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。"
+        ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
+            Return "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
+        ElseIf TypeOf InnerEx Is OutOfMemoryException Then
+            Return "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。"
+        ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
+            Return "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
+        ElseIf TypeOf InnerEx Is SocketException AndAlso DescList.Any(Function(l) l.Contains("WSAStartup")) Then
+            Return "请尝试卸载中国移动云盘,然后再试。"
+        ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
+                "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) DescList.Any(Function(l) l.Contains(s))) Then
+            Return "你的网络环境不佳,导致难以连接到服务器。请稍后重试,或使用 VPN 以改善网络环境。"
+        Else
+            Return Nothing
+        End If
+    End Function
 
     ''' <summary>
     ''' 返回一个枚举对应的字符串。
@@ -1551,12 +1541,12 @@ 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
             Dim Length As Integer = If(Data, "").Length
-            Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data))
+            Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data), ex)
         End Try
     End Function
 
@@ -2131,6 +2121,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 +2182,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 +2228,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 +2468,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
@@ -2625,10 +2631,6 @@ Retry:
     ''' 相对增减控件的左边距。
     ''' </summary>
     Public Sub DeltaLeft(control As FrameworkElement, newValue As Double)
-        '安全性检查
-        DebugAssert(Not Double.IsNaN(newValue))
-        DebugAssert(Not Double.IsInfinity(newValue))
-
         If TypeOf control Is Window Then
             '窗口改变
             CType(control, Window).Left += newValue
@@ -2640,8 +2642,6 @@ Retry:
                 Case HorizontalAlignment.Right
                     control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right - newValue, control.Margin.Bottom)
                     'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom)
-                Case Else
-                    DebugAssert(False)
             End Select
         End If
     End Sub
@@ -2649,17 +2649,12 @@ Retry:
     ''' 设置控件的左边距。(仅针对置左控件)
     ''' </summary>
     Public Sub SetLeft(control As FrameworkElement, newValue As Double)
-        DebugAssert(control.HorizontalAlignment = HorizontalAlignment.Left)
         control.Margin = New Thickness(newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom)
     End Sub
     ''' <summary>
     ''' 相对增减控件的上边距。
     ''' </summary>
     Public Sub DeltaTop(control As FrameworkElement, newValue As Double)
-        '安全性检查
-        DebugAssert(Not Double.IsNaN(newValue))
-        DebugAssert(Not Double.IsInfinity(newValue))
-
         If TypeOf control Is Window Then
             '窗口改变
             CType(control, Window).Top += newValue
@@ -2671,11 +2666,8 @@ Retry:
                 Case VerticalAlignment.Bottom
                     control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, control.Margin.Bottom - newValue)
                     'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom)
-                Case Else
-                    DebugAssert(False)
             End Select
         End If
-
         'If Double.IsNaN(newValue) OrElse Double.IsInfinity(newValue) Then Return '安全性检查
         'Select Case control.VerticalAlignment
         '  Case VerticalAlignment.Top, VerticalAlignment.Stretch, VerticalAlignment.Center
@@ -2689,7 +2681,6 @@ Retry:
     ''' 设置控件的顶边距。(仅针对置上控件)
     ''' </summary>
     Public Sub SetTop(control As FrameworkElement, newValue As Double)
-        DebugAssert(control.VerticalAlignment = VerticalAlignment.Top)
         control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom)
     End Sub
 
@@ -2760,11 +2751,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 +2913,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)
@@ -2973,6 +2964,13 @@ Retry:
                 End If
         End Select
 
+        '上报
+        If Level >= LogLevel.Hint Then
+            Telemetry("PCL 错误",
+                      "Level", GetStringFromEnum(Level),
+                      "Title", FilterUserName(FilterAccessToken(Title, "*"), "*"),
+                      "Exception", FilterUserName(FilterAccessToken(Text, "*"), "*"))
+        End If
     End Sub
     ''' <summary>
     ''' 输出错误信息。
@@ -2986,7 +2984,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)
@@ -3039,6 +3037,13 @@ Retry:
                 End If
         End Select
 
+        '上报
+        If Level >= LogLevel.Hint Then
+            Telemetry("PCL 异常",
+                      "Level", GetStringFromEnum(Level),
+                      "Title", FilterUserName(FilterAccessToken(Title, "*"), "*"),
+                      "Exception", FilterUserName(FilterAccessToken(ExFull, "*"), "*"))
+        End If
     End Sub
 
     '反馈
@@ -3048,7 +3053,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
@@ -3075,9 +3080,23 @@ Retry:
             "文件位置:" & Path)
     End Sub
 
-    '断言
-    Public Sub DebugAssert(Exp As Boolean)
-        If Not Exp Then Throw New Exception("断言命中")
+    '遥测
+    Public Sub Telemetry([Event] As String, ParamArray Datas As String())
+        If Not Setup.Get("SystemSystemTelemetry") Then Return '用户关闭了遥测
+        If Not ClsBaseUrl.StartsWithF("http") Then Return '开源版没有设置遥测地址
+        RunInNewThread(
+        Sub()
+            Try
+                Log($"[System] 匿名数据上报:{[Event]}")
+                Dim Url As String = $"{ClsBaseUrl}&Event={WebUtility.UrlEncode([Event])}"
+                For i = 0 To Datas.Length - 1 Step 2
+                    Url &= "&" & WebUtility.UrlEncode(Datas(i)) & "=" & WebUtility.UrlEncode(Datas(i + 1).Replace(vbCrLf, vbLf))
+                Next
+                NetRequestByClient(Url, MakeLog:=False)
+            Catch
+                Log("[System] 匿名数据上报失败", LogLevel.Debug)
+            End Try
+        End Sub, "Telemetry", ThreadPriority.Lowest)
     End Sub
 
     '获取当前的堆栈信息

+ 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()

Разница между файлами не показана из-за своего большого размера
+ 411 - 622
Plain Craft Launcher 2/Modules/Base/ModNet.vb


+ 37 - 25
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")
@@ -1137,6 +1137,11 @@ Retry:
             If ModrinthThread IsNot Nothing Then ModrinthThread.Join()
             If Task.IsAborted Then Return
 
+            '筛除不是 Forge 的 Mod
+            If IsOldForgeRequest Then
+                RawResults = RawResults.Where(Function(p) Not p.ModLoaders.Any() OrElse p.ModLoaders.Contains(CompModLoaderType.Forge)).ToList
+            End If
+
             '确保存在结果
             Storage.ErrorMessage = Nothing
             If Not RawResults.Any() Then
@@ -1167,11 +1172,6 @@ Retry:
             ModrinthThread?.Interrupt()
         End Try
 
-        '筛除不是 Forge 的 Mod
-        If IsOldForgeRequest Then
-            RawResults = RawResults.Where(Function(p) Not p.ModLoaders.Any() OrElse p.ModLoaders.Contains(CompModLoaderType.Forge)).ToList
-        End If
-
 #End Region
 
 #Region "提取非重复项,存储于 RealResults"
@@ -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 的时候会添加缓存以便之后读取

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

@@ -2,7 +2,9 @@
 
     '构造函数
     Private TempFolder As String
-    Public Sub New(UUID As Integer)
+    Private TargetVersion As McVersion
+    Public Sub New(Optional TargetVersion As McVersion = Nothing)
+        Me.TargetVersion = TargetVersion
         '构建文件结构
         TempFolder = RequestTaskTempFolder()
         Directory.CreateDirectory(TempFolder & "Temp\")
@@ -339,6 +341,7 @@ Extracted:
     ''' 导致崩溃的原因枚举。
     ''' </summary>
     Private Enum CrashReason
+        Java虚拟机参数有误
         Mod文件被解压
         MixinBootstrap缺失
         内存不足
@@ -482,10 +485,12 @@ Done:
         '崩溃报告分析,高优先级
         If LogCrash IsNot Nothing Then
             If LogCrash.Contains("Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass") Then AppendReason(CrashReason.Java版本过高)
+            If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)})
         End If
 
         '游戏日志分析
         If LogMc IsNot Nothing Then
+            If LogMc.Contains("Unrecognized option:") Then AppendReason(CrashReason.Java虚拟机参数有误)
             If LogMc.Contains("Found multiple arguments for option fml.forgeVersion, but you asked for only one") Then AppendReason(CrashReason.版本Json中存在多个Forge)
             If LogMc.Contains("The driver does not appear to support OpenGL") Then AppendReason(CrashReason.显卡不支持OpenGL)
             If LogMc.Contains("java.lang.ClassCastException: java.base/jdk") Then AppendReason(CrashReason.使用JDK)
@@ -508,13 +513,13 @@ Done:
             If LogMc.Contains("Couldn't set pixel format") Then AppendReason(CrashReason.显卡驱动不支持导致无法设置像素格式)
             If LogMc.Contains("java.lang.OutOfMemoryError") OrElse LogMc.Contains("an out of memory error") Then AppendReason(CrashReason.内存不足)
             If LogMc.Contains("java.lang.RuntimeException: Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders.") Then AppendReason(CrashReason.ShadersMod与OptiFine同时安装)
-            If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容)
+            If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") OrElse LogMc.Contains("java.lang.NoSuchMethodError: 'void sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容)
             If LogMc.Contains("1282: Invalid operation") Then AppendReason(CrashReason.光影或资源包导致OpenGL1282错误)
             If LogMc.Contains("signer information does not match signer information of other classes in the same package") Then AppendReason(CrashReason.文件或内容校验失败, If(RegexSeek(LogMc, "(?<=class "")[^']+(?=""'s signer information)"), "").TrimEnd(vbCrLf))
             If LogMc.Contains("Maybe try a lower resolution resourcepack?") Then AppendReason(CrashReason.材质过大或显卡配置不足)
             If LogMc.Contains("java.lang.NoSuchMethodError: net.minecraft.world.server.ChunkManager$ProxyTicketManager.shouldForceTicks(J)Z") AndAlso LogMc.Contains("OptiFine") Then AppendReason(CrashReason.OptiFine导致无法加载世界)
             If LogMc.Contains("Unsupported class file major version") Then AppendReason(CrashReason.Java版本不兼容)
-            If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") Then AppendReason(CrashReason.NightConfig的Bug)
+            If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") AndAlso Not CrashReasons.ContainsKey(CrashReason.Mod配置文件导致游戏崩溃) Then AppendReason(CrashReason.NightConfig的Bug)
             If LogMc.Contains("Cannot find launch target fmlclient, unable to launch") Then AppendReason(CrashReason.Forge安装不完整)
             If LogMc.Contains("Invalid paths argument, contained no existing paths") AndAlso LogMc.Contains("libraries\net\minecraftforge\fmlcore") Then AppendReason(CrashReason.Forge安装不完整)
             If LogMc.Contains("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符)
@@ -576,7 +581,6 @@ Done:
             End If
             If LogCrash.Contains("Multiple entries with same key: ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Multiple entries with same key: )[^=]+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
             If LogCrash.Contains("LoaderExceptionModCrash: Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=LoaderExceptionModCrash: Caught exception from )[^\n]+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
-            If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)})
         End If
 
     End Sub
@@ -856,12 +860,13 @@ NextStack:
     ''' <summary>
     ''' 弹出崩溃弹窗,并指导导出崩溃报告。
     ''' </summary>
-    Public Sub Output(IsHandAnalyze As Boolean, Optional ExtraFiles As List(Of String) = Nothing)
+    Public Sub Output(IsManualAnalyze As Boolean, Optional ExtraFiles As List(Of String) = Nothing)
         '弹窗提示
         FrmMain.ShowWindowToTop()
-        Select Case MyMsgBox(GetAnalyzeResult(IsHandAnalyze), If(IsHandAnalyze, "错误报告分析结果", "Minecraft 出现错误"),
-            "确定", If(IsHandAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsHandAnalyze, "", "导出错误报告"),
-            Button2Action:=If(IsHandAnalyze OrElse DirectFile Is Nothing, Nothing,
+        Dim Detail As String = GetAnalyzeResult(IsManualAnalyze)
+        Select Case MyMsgBox(Detail, If(IsManualAnalyze, "错误报告分析结果", "Minecraft 出现错误"),
+            "确定", If(IsManualAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsManualAnalyze, "", "导出错误报告"),
+            Button2Action:=If(IsManualAnalyze OrElse DirectFile Is Nothing, Nothing,
             Sub()
                 '弹窗选择:查看日志
                 If File.Exists(DirectFile.Value.Key) Then
@@ -918,6 +923,11 @@ NextStack:
                 End Try
                 OpenExplorer(FileAddress)
         End Select
+        '上报
+        If IsManualAnalyze Then Return
+        Telemetry("Minecraft 崩溃",
+                  "Version", If(TargetVersion Is Nothing, "null", TargetVersion.Version.ToString),
+                  "Detail", FilterUserName(Detail, "*"))
     End Sub
     ''' <summary>
     ''' 获取崩溃分析的结果描述。
@@ -938,6 +948,8 @@ NextStack:
         For Each Reason In CrashReasons
             Dim Additional As List(Of String) = Reason.Value
             Select Case Reason.Key
+                Case CrashReason.Java虚拟机参数有误
+                    Results.Add("由于 Java 虚拟机参数有误,导致游戏无法继续运行。\n请检查高级启动选项中设置的 Java 虚拟机参数是否有误。")
                 Case CrashReason.Mod文件被解压
                     Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。")
                 Case CrashReason.内存不足
@@ -1018,9 +1030,14 @@ NextStack:
                     End If
                 Case CrashReason.特定实体导致崩溃
                     If Additional.Count = 1 Then
-                        Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况:\n - 若正常运行,则是该实体导致出错,你或许需要使用一些方式删除此实体。\n - 若仍然出错,问题就可能来自其他原因……\h")
+                        Dim entityInfo As String = Additional.First
+                        If entityInfo.StartsWith("minecraft:player") Then
+                            Results.Add("游戏似乎因为玩家实体 " & entityInfo & " 出现了问题。\n\n这通常是因为玩家数据损坏或世界存档问题导致的。\n请尝试以下解决方案:\n - 创建一个新世界,观察游戏是否正常运行\n - 如果新世界正常,可能需要删除或修复当前世界的玩家数据文件\n - 也可能是某个 Mod 与玩家实体处理存在冲突,尝试逐个禁用近期安装的 Mod\n - 如果问题持续存在,建议备份重要数据后重新创建世界\h")
+                        Else
+                            Results.Add("游戏似乎因为实体 " & entityInfo & " 出现了问题。\n\n请尝试以下解决方案:\n - 创建一个新世界,并尝试生成该类型的实体,观察游戏运行情况\n - 如果新世界中该实体正常,问题可能在于当前世界的数据损坏\n - 如果新世界中仍然崩溃,可能是某个 Mod 导致了实体处理异常\n - 尝试逐个禁用可能影响实体的 Mod(如生物类、AI 类 Mod)\n - 也可以尝试使用指令删除该坐标附近的实体\h")
+                        End If
                     Else
-                        Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,观察游戏的运行情况:\n - 若正常运行,则是某些实体导致出错,你或许需要删除该世界。\n - 若仍然出错,问题就可能来自其他原因……\h")
+                        Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n请尝试以下解决方案:\n - 创建一个新世界,观察游戏的运行情况\n - 如果新世界正常运行,则是当前世界的实体数据导致出错\n - 可以尝试使用指令清理异常实体\n - 如果问题持续存在,可能是某个处理实体的 Mod 存在问题\n - 建议逐个禁用近期安装的 Mod,特别是涉及生物、AI 或实体处理的 Mod\h")
                     End If
                 Case CrashReason.OptiFine与Forge不兼容
                     Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")
@@ -1102,7 +1119,7 @@ NextStack:
                 If(Not Results.Any(Function(r) r.EndsWithF("\h")) OrElse IsHandAnalyze, "",
                     vbCrLf & "如果要寻求帮助,请把错误报告文件发给对方,而不是发送这个窗口的照片或者截图。" &
                     If(If(PageSetupSystem.IsLauncherNewest(), True), "",
-                    vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 设置 → 启动器 → 检查更新 来更新 PCL。"))
+                    vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 [设置 → 其他 → 启动器 → 检查更新] 更新 PCL。"))
     End Function
 
 End Class

+ 51 - 80
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]?)?")
@@ -594,7 +594,7 @@
             VersionName = Version
             Me.Version = New Version(Version)
             Me.Inherit = Inherit
-            FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch)
+            FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch)
         End Sub
     End Class
 
@@ -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,72 +1100,37 @@
 #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))
-        '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 McimUrl As String = DlSourceModGet(Url)
+        If McimUrl <> Url Then
+            Select Case Setup.Get("ToolDownloadMod")
+                Case 0
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
+                Case 1
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) '至少 10s,要不然有时候远端服务器来不及完成
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
+                Case Else
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
+                    Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
+            End Select
+        Else
+            Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10))
+            Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
+        End If
         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
@@ -1269,7 +1233,14 @@
 
     'Mod 下载源
     Public Function DlSourceModGet(Original As String) As String
-        Return Original
+        Return Original.
+            Replace("api.modrinth.com", "mod.mcimirror.top/modrinth").
+            Replace("staging-api.modrinth.com", "mod.mcimirror.top/modrinth").
+            Replace("cdn.modrinth.com", "mod.mcimirror.top").
+            Replace("api.curseforge.com", "mod.mcimirror.top/curseforge").
+            Replace("edge.forgecdn.net", "mod.mcimirror.top").
+            Replace("mediafilez.forgecdn.net", "mod.mcimirror.top").
+            Replace("media.forgecdn.net", "mod.mcimirror.top")
     End Function
 
     'Loader 自动切换

+ 12 - 7
Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb

@@ -117,6 +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("无法运行该 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"
@@ -459,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
@@ -529,6 +532,8 @@ NoUserJava:
                 If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705)
                 JavaSearchFolder(Disk.Name, JavaPreList, False)
             Next
+            '查找 .jdks 文件夹中的 Java
+            JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\.jdks\", JavaPreList, False, True)
             '查找 APPDATA 文件夹中的 Java
             JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\", JavaPreList, False)
             JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) & "\", JavaPreList, False)
@@ -677,14 +682,14 @@ Wait:
             '若该目录有 Java,则加入结果
             If File.Exists(Path & "javaw.exe") Then Results(Path) = Source
             '查找其下的所有文件夹
-            '不应使用网易的 Java:https://github.com/Hex-Dragon/PCL2/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",
                             "runtime", "x86", "x64", "forge", "原版", "optifine", "官方", "启动", "hmcl", "mod", "高清",
                             "download", "launch", "程序", "path", "version", "baka", "pcl", "zulu", "local", "packages",
-                            "4297127d64ec6", "1.", "启动"}
+                            "4297127d64ec6", "1.", "启动", "jbr-"}
             For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories
                 If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接
                 Dim SearchEntry = GetFolderNameFromPath(FolderInfo.Name).ToLower '用于搜索的字符串
@@ -745,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)
@@ -757,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")

+ 120 - 76
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -66,7 +66,7 @@ Public Module ModLaunch
     ''' </summary>
     Public Sub McLaunchLog(Text As String)
         Text = FilterUserName(FilterAccessToken(Text, "*"), "*")
-        RunInUi(Sub() FrmLaunchRight.LabLog.Text += vbCrLf & "[" & GetTimeNow() & "] " & Text)
+        RunInUi(Sub() FrmLaunchRight.LabLog.Text += $"{vbCrLf}[{GetTimeNow()}] {Text}")
         Log("[Launch] " & Text)
     End Sub
 
@@ -101,6 +101,8 @@ Public Module ModLaunch
             If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Critical)
             Throw
         End Try
+        Dim IsSavingBatch As Boolean = CurrentLaunchOptions?.SaveBatch IsNot Nothing
+
         '正式加载
         Try
             '构造主加载器
@@ -145,9 +147,15 @@ Public Module ModLaunch
             Select Case LaunchLoader.State
                 Case LoadState.Finished
                     Hint(McVersionCurrent.Name & " 启动成功!", HintType.Finish)
+                    '上报
+                    If Not IsSavingBatch Then
+                        Telemetry("Minecraft 启动成功",
+                              "Version", McVersionCurrent.Version.ToString,
+                              "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType)))
+                    End If
                 Case LoadState.Aborted
                     If AbortHint Is Nothing Then
-                        Hint(If(CurrentLaunchOptions?.SaveBatch Is Nothing, "已取消启动!", "已取消导出启动脚本!"), HintType.Info)
+                        Hint(If(IsSavingBatch, "已取消导出启动脚本!", "已取消启动!"), HintType.Info)
                     Else
                         Hint(AbortHint, HintType.Finish)
                     End If
@@ -162,7 +170,7 @@ NextInner:
             If CurrentEx.Message.StartsWithF("$") Then
                 '若有以 $ 开头的错误信息,则以此为准显示提示
                 '若错误信息为 $$,则不提示
-                If Not CurrentEx.Message = "$$" Then MyMsgBox(CurrentEx.Message.TrimStart("$"), If(CurrentLaunchOptions?.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败"))
+                If Not CurrentEx.Message = "$$" Then MyMsgBox(CurrentEx.Message.TrimStart("$"), If(IsSavingBatch, "导出启动脚本失败", "启动失败"))
                 Throw
             ElseIf CurrentEx.InnerException IsNot Nothing Then
                 '检查下一级错误
@@ -172,8 +180,15 @@ NextInner:
                 '没有特殊处理过的错误信息
                 McLaunchLog("错误:" & GetExceptionDetail(ex))
                 Log(ex,
-                    If(CurrentLaunchOptions?.SaveBatch Is Nothing, "Minecraft 启动失败", "导出启动脚本失败"), LogLevel.Msgbox,
-                    If(CurrentLaunchOptions?.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败"))
+                    If(IsSavingBatch, "导出启动脚本失败", "Minecraft 启动失败"), LogLevel.Msgbox,
+                    If(IsSavingBatch, "导出启动脚本失败", "启动失败"))
+                '上报
+                If Not IsSavingBatch Then
+                    Telemetry("Minecraft 启动失败",
+                              "Version", McVersionCurrent.Version.ToString,
+                              "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType)),
+                              "Exception", FilterUserName(FilterAccessToken(GetExceptionDetail(ex), "*"), "*"))
+                End If
                 Throw
             End If
         End Try
@@ -684,11 +699,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 +715,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 +725,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 +752,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
@@ -840,7 +852,7 @@ LoginFinish:
                                             If(Data.Input.UserName.Contains("@"), "", " - 登录账号应为邮箱或统一通行证账号,而非游戏角色 ID。" & vbCrLf) &
                                             " - 只注册了账号,但没有加入对应服务器。")
                 End Select
-            ElseIf AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout") OrElse AllMessage.Contains("网络请求失败") Then
+            ElseIf AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout") OrElse AllMessage.Contains("网络请求失败") OrElse AllMessage.Contains("408") OrElse AllMessage.Contains("由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。") Then
                 Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后重试,或使用加速器或 VPN 以改善网络环境。")
             ElseIf ex.Message.StartsWithF("$") Then
                 Throw
@@ -858,8 +870,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",
+            ContentType:="application/x-www-form-urlencoded"))
         McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString)
 
         '弹窗
@@ -888,13 +901,22 @@ 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)
-        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
+            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 ResponsedWebException
+            Dim Response = ex.Response
+            If Response.ContainsF("must sign in again", True) OrElse Response.ContainsF("password expired", True) OrElse
+               (Response.Contains("refresh_token") AndAlso Response.Contains("is not valid")) Then '#269
                 Return {"Relogin", ""}
+            ElseIf Response.Contains("Account security interrupt") Then
+                MyMsgBox("该账号由于安全问题无法登陆,请前往微软账户页获取更多信息。", "登录失败", "我知道了", IsWarn:=True)
+                Throw New Exception("$$")
+            ElseIf Response.Contains("service abuse") Then
+                MyMsgBox("非常抱歉,该账号已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True)
+                Throw New Exception("$$")
             Else
                 Throw
             End If
@@ -918,8 +940,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,21 +962,22 @@ Retry:
                                  }"
         Dim Result As String
         Try
-            Result = NetRequestMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", "POST", Request, "application/json", 3)
-        Catch ex As Net.WebException
+            Result = NetRequestByClientMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post,
+                Content:=Request, ContentType:="application/json")
+        Catch ex As ResponsedWebException
             '参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js
-            If ex.Message.Contains("2148916227") Then
+            If ex.Response.Contains("2148916227") Then
                 MyMsgBox("该账号似乎已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True)
                 Throw New Exception("$$")
-            ElseIf ex.Message.Contains("2148916233") Then
+            ElseIf ex.Response.Contains("2148916233") Then
                 If MyMsgBox("你尚未注册 Xbox 账户,请在注册后再登录。", "登录提示", "注册", "取消") = 1 Then
                     OpenWebsite("https://signup.live.com/signup")
                 End If
                 Throw New Exception("$$")
-            ElseIf ex.Message.Contains("2148916235") Then
+            ElseIf ex.Response.Contains("2148916235") Then
                 MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请使用加速器或 VPN。", "登录失败", "我知道了")
                 Throw New Exception("$$")
-            ElseIf ex.Message.Contains("2148916238") Then
+            ElseIf ex.Response.Contains("2148916238") Then
                 If MyMsgBox("该账号年龄不足,你需要先修改出生日期,然后才能登录。" & vbCrLf &
                             "该账号目前填写的年龄是否在 13 岁以上?", "登录提示", "13 岁以上", "12 岁以下", "我不知道") = 1 Then
                     OpenWebsite("https://account.live.com/editprof.aspx")
@@ -971,10 +994,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,14 +1011,15 @@ 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
-                Log(ex, "微软登录第 5 步汇报 429")
+                Log(ex, "微软登录第 4 步汇报 429")
                 Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
             ElseIf Message.Contains("(403)") Then
-                Log(ex, "微软登录第 5 步汇报 403")
+                Log(ex, "微软登录第 4 步汇报 403")
                 Throw New Exception("$当前 IP 的登录尝试异常。" & vbCrLf & "如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!")
             Else
                 Throw
@@ -1005,7 +1034,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
@@ -1016,7 +1046,7 @@ Retry:
                 Throw New Exception("$$")
             End If
         Catch ex As Exception
-            Log(ex, "微软登录第 6 步异常:" & Result)
+            Log(ex, "微软登录第 5 步异常:" & Result)
             Throw
         End Try
     End Sub
@@ -1026,14 +1056,15 @@ 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
-                Log(ex, "微软登录第 7 步汇报 429")
+                Log(ex, "微软登录第 6 步汇报 429")
                 Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
             ElseIf Message.Contains("(404)") Then
-                Log(ex, "微软登录第 7 步汇报 404")
+                Log(ex, "微软登录第 6 步汇报 404")
                 RunInNewThread(
                 Sub()
                     Select Case MyMsgBox("请先创建 Minecraft 玩家档案,然后再重新登录。", "登录失败", "创建档案", "取消")
@@ -1099,7 +1130,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 +1236,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
@@ -1349,23 +1386,13 @@ Retry:
             McLaunchLog("新版 Game 参数获取成功")
         End If
         '编码参数(#4700、#5892、#5909)
+        If McLaunchJavaSelected.VersionCode >= 18 Then 'Dfile.encoding 需要放在 Dstdout.encoding 后面(#6934)
+            If Not Arguments.Contains("-Dfile.encoding=") Then Arguments = "-Dfile.encoding=COMPAT " & Arguments
+        End If
         If McLaunchJavaSelected.VersionCode > 8 Then
             If Not Arguments.Contains("-Dstdout.encoding=") Then Arguments = "-Dstdout.encoding=UTF-8 " & Arguments
             If Not Arguments.Contains("-Dstderr.encoding=") Then Arguments = "-Dstderr.encoding=UTF-8 " & Arguments
         End If
-        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 +1401,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,11 +1467,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") '信任系统根证书(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)))
@@ -1493,11 +1537,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") '信任系统根证书(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)))
@@ -1531,10 +1576,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
@@ -1671,6 +1714,7 @@ NextVersion:
         GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${user_type}", "msa") '#1221
+        GameArguments.Add("${primary_jar}", Version.Path & Version.Name & ".jar") '#6942
 
         '窗口尺寸参数
         Dim GameSize As Size

+ 111 - 88
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
@@ -968,7 +989,7 @@ ExitDataLoad:
             If ToString = "" Then
                 Return "原版 " & McName
             Else
-                Return McName & ToString & If(ModeDebug, " (" & SortCode & "#)", "")
+                Return McName & ToString
             End If
         End Function
 
@@ -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
@@ -1610,7 +1631,8 @@ OnLoaded:
         If SkinJson("textures") Is Nothing OrElse SkinJson("textures")("skin") Is Nothing OrElse SkinJson("textures")("skin")("url") Is Nothing Then
             Throw New Exception("用户未设置自定义皮肤")
         Else
-            SkinValue = SkinJson("textures")("skin")("url").ToString.Replace("http:", "https:")
+            Dim SkinUrl As String = SkinJson("textures")("skin")("url").ToString
+            SkinValue = If(SkinUrl.Contains("minecraft.net/"), SkinUrl.Replace("http://", "https://"), SkinUrl)
         End If
         '保存缓存
         WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid, SkinValue)
@@ -1627,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
@@ -1772,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
@@ -1945,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
@@ -1968,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
@@ -2016,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
@@ -2193,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
@@ -2392,7 +2415,7 @@ NextEntry:
     Public Function FilterUserName(Raw As String, FilterChar As Char) As String
         If Raw.Contains(":\Users\") Then
             For Each Token In RegexSearch(Raw, "(?<=:\\Users\\)[^\\]+")
-                Raw = Raw.Replace(Token, New String(FilterChar, Token.Count))
+                Raw = Raw.Replace("\" & Token, "\" & New String(FilterChar, Token.Count))
             Next
         End If
         Return Raw

+ 14 - 15
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
 
@@ -688,7 +688,7 @@ Finished:
         ''' </summary>
         Public ReadOnly Property CanUpdate As Boolean
             Get
-                Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso ChangelogUrls.Any()
+                Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso Not Setup.Get("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version) AndAlso ChangelogUrls.Any()
             End Get
         End Property
 
@@ -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)

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

@@ -158,6 +158,7 @@ Retry:
         If File.Exists(OverridesIni) Then
             WriteIni(OverridesIni, "VersionArgumentIndie", 1) '开启版本隔离
             WriteIni(OverridesIni, "VersionArgumentIndieV2", True)
+            WriteIni(OverridesIni, "IsStar", False)
             CopyFile(OverridesIni, VersionIni) '覆写已有的 ini
         Else
             WriteIni(VersionIni, "VersionArgumentIndie", 1) '开启版本隔离
@@ -244,7 +245,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

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

@@ -349,14 +349,14 @@
             State = MinecraftState.Crashed
             '崩溃分析
             WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析")
-            Hint("检测到 Minecraft 出现错误,错误分析已开始……")
+            Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Critical)
             FeedbackInfo()
             RunInNewThread(
             Sub()
                 Try
                     Thread.Sleep(2000)
                     WatcherLog("崩溃分析开始")
-                    Dim Analyzer As New CrashAnalyzer(PID)
+                    Dim Analyzer As New CrashAnalyzer(Version)
                     Analyzer.Collect(Version.PathIndie, LatestLog.ToList)
                     Analyzer.Prepare()
                     Analyzer.Analyze(Version)

+ 4 - 0
Plain Craft Launcher 2/Modules/ModEvent.vb

@@ -115,6 +115,10 @@
                         PageOtherTest.StartCustomDownload(Data(0), "未知")
                     End Try
 
+                Case "修改设置"
+                    Setup.Set(Data(0), Data(1))
+                    Hint($"已写入设置:{Data(0)} → {Data(1)}", HintType.Finish)
+
                 Case Else
                     MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败")
             End Select

+ 1 - 1
Plain Craft Launcher 2/Modules/ModMusic.vb

@@ -247,7 +247,7 @@
                 Log("[Music] 已恢复播放")
                 Try
                     MusicNAudio?.Play()
-                Catch 'https://github.com/Hex-Dragon/PCL2/pull/5415#issuecomment-2751135223
+                Catch 'https://github.com/Meloong-Git/PCL/pull/5415#issuecomment-2751135223
                     MusicNAudio?.Stop()
                     MusicNAudio?.Play()
                 End Try

+ 21 - 27
Plain Craft Launcher 2/Modules/ModSecret.vb

@@ -9,9 +9,11 @@ Friend Module ModSecret
     '在开源版的注册表与常规版的注册表隔离,以防数据冲突
     Public Const RegFolder As String = "PCLDebug"
     '用于微软登录的 ClientId
-    Public Const OAuthClientId As String = ""
+    Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "")
     'CurseForge API Key
-    Public Const CurseForgeAPIKey As String = ""
+    Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "")
+    '用于匿名数据收集的腾讯云日志服务上报 URL,形如 https://{region}.cls.tencentcs.com/track?topic_id={topic_id}
+    Public Const ClsBaseUrl As String = ""
 
     Friend Sub SecretOnApplicationStart()
         '提升 UI 线程优先级
@@ -71,37 +73,23 @@ 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
     ''' <summary>
     ''' 设置 Headers 的 UA、Referer。
     ''' </summary>
-    Friend Sub SecretHeadersSign(Url As String, ByRef Client As WebClient, Optional UseBrowserUserAgent As Boolean = False)
+    Friend Sub SecretHeadersSign(Url As String, ByRef Req As HttpRequestMessage, Optional UseBrowserUserAgent As Boolean = False)
         If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then
-            Client.Headers("User-Agent") = "LogStatistic" '#4951
+            Req.Headers.Add("User-Agent", "LogStatistic")  '#4951
         ElseIf UseBrowserUserAgent Then
-            Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
+            Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode} Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36")
         Else
-            Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode
+            Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode}")
         End If
-        Client.Headers("Referer") = "http://" & VersionCode & ".open.pcl2.server/"
-        If Url.Contains("api.curseforge.com") Then Client.Headers("x-api-key") = CurseForgeAPIKey
-    End Sub
-    ''' <summary>
-    ''' 设置 Headers 的 UA、Referer。
-    ''' </summary>
-    Friend Sub SecretHeadersSign(Url As String, ByRef Request As HttpWebRequest, Optional UseBrowserUserAgent As Boolean = False)
-        If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then
-            Request.UserAgent = "LogStatistic" '#4951
-        ElseIf UseBrowserUserAgent Then
-            Request.UserAgent = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
-        Else
-            Request.UserAgent = "PCL2/" & VersionStandardCode
-        End If
-        Request.Referer = "http://" & VersionCode & ".open.pcl2.server/"
-        If Url.Contains("api.curseforge.com") Then Request.Headers("x-api-key") = CurseForgeAPIKey
+        Req.Headers.Add("Referer", $"http://{VersionCode}.open.pcl2.server/")
+        If Url.Contains("api.curseforge.com") Then Req.Headers.Add("x-api-key", CurseForgeAPIKey)
     End Sub
 
 #End Region
@@ -225,11 +213,17 @@ Friend Module ModSecret
             If Not FrmMain.IsLoaded Then Return
             '顶部条背景
             Dim Brush = New LinearGradientBrush With {.EndPoint = New Point(1, 0), .StartPoint = New Point(0, 0)}
-            Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue - 21, ColorSat, 53 + ColorLightAdjust)})
-            Brush.GradientStops.Add(New GradientStop With {.Offset = 0.33, .Color = New MyColor().FromHSL2(ColorHue - 7, ColorSat, 47 + ColorLightAdjust)})
-            Brush.GradientStops.Add(New GradientStop With {.Offset = 0.67, .Color = New MyColor().FromHSL2(ColorHue + 7, ColorSat, 47 + ColorLightAdjust)})
-            Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + 21, ColorSat, 53 + ColorLightAdjust)})
+            If TypeOf ColorHueTopbarDelta Is Integer Then
+                Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue - ColorHueTopbarDelta, ColorSat, 48 + ColorLightAdjust)})
+                Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 54 + ColorLightAdjust)})
+                Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta, ColorSat, 48 + ColorLightAdjust)})
+            Else
+                Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(0), ColorSat, 48 + ColorLightAdjust)})
+                Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(1), ColorSat, 54 + ColorLightAdjust)})
+                Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(2), ColorSat, 48 + ColorLightAdjust)})
+            End If
             FrmMain.PanTitle.Background = Brush
+            FrmMain.PanTitle.Background.Freeze()
             '主页面背景
             If Setup.Get("UiBackgroundColorful") Then
                 Brush = New LinearGradientBrush With {.EndPoint = New Point(0.1, 1), .StartPoint = New Point(0.9, 0)}

+ 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

+ 151 - 0
Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb

@@ -0,0 +1,151 @@
+'来源:https://blog.csdn.net/simpleman2000/article/details/140952294
+
+Imports System.ComponentModel
+Imports System.Runtime.InteropServices
+Imports System.Windows.Interop
+
+Public Class DragHelper
+
+    Public Event DragDrop As EventHandler
+
+    Public Property DropFilePaths As String()
+        Get
+            Return _DropFilePathsBackingField
+        End Get
+        Private Set(value As String())
+            _DropFilePathsBackingField = value
+        End Set
+    End Property
+    Private _DropFilePathsBackingField As String()
+
+    Public Property DropPoint As POINT
+        Get
+            Return _DropPointBackingField
+        End Get
+        Private Set(value As POINT)
+            _DropPointBackingField = value
+        End Set
+    End Property
+    Private _DropPointBackingField As POINT
+
+    Public Property HwndIntPtrSource As HwndSource
+
+    Public Sub AddHook()
+        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()
+        HwndIntPtrSource.RemoveHook(AddressOf WndProc)
+        DragAcceptFiles(HwndIntPtrSource.Handle, False)
+    End Sub
+
+    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 New POINT()
+
+        If TryGetDropInfo(msg, wParam, filePaths, point) Then
+            DropPoint = point
+            DropFilePaths = filePaths
+            RaiseEvent DragDrop(Me, EventArgs.Empty)
+            handled = True
+        End If
+        Return IntPtr.Zero
+    End Function
+
+    <DllImport("user32.dll", SetLastError:=True)>
+    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(msg As UInteger, flags As UInteger) As Boolean
+    End Function
+
+    <DllImport("shell32.dll")>
+    Private Shared Sub DragAcceptFiles(hWnd As IntPtr, fAccept As Boolean)
+    End Sub
+
+    <DllImport("shell32.dll", CharSet:=CharSet.Unicode)>
+    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(hDrop As IntPtr, ByRef lppt As POINT) As Boolean
+    End Function
+
+    <DllImport("shell32.dll")>
+    Private Shared Sub DragFinish(hDrop As IntPtr)
+    End Sub
+
+    <DllImport("ole32.dll")>
+    Private Shared Function RevokeDragDrop(hWnd As IntPtr) As Integer
+    End Function
+
+    <DllImport("shell32.dll")>
+    Private Shared Function IsUserAnAdmin() As Boolean
+    End Function
+
+    <StructLayout(LayoutKind.Sequential)>
+    Public Structure POINT
+        Public X As Integer
+        Public Y As Integer
+    End Structure
+
+    <StructLayout(LayoutKind.Sequential)>
+    Private Structure CHANGEFILTERSTRUCT
+        Public cbSize As UInteger
+        Public ExtStatus As UInteger
+    End Structure
+
+    Private Const WM_COPYGLOBALDATA As UInteger = &H49
+    Private Const WM_COPYDATA As UInteger = &H4A
+    Private Const WM_DROPFILES As UInteger = &H233
+    Private Const MSGFLT_ALLOW As UInteger = 1
+    Private Const MSGFLT_ADD As UInteger = 1
+    Private Const MAX_PATH As Integer = 260
+
+    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 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
+                    [error] = Not ChangeWindowMessageFilterEx(handle, msg, MSGFLT_ALLOW, status)
+                Else
+                    [error] = Not ChangeWindowMessageFilter(msg, MSGFLT_ADD)
+                End If
+
+                If [error] Then Throw New Win32Exception(Marshal.GetLastWin32Error())
+            Next
+        End If
+    End Sub
+
+    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()
+
+        If msg <> WM_DROPFILES Then Return False
+
+        Dim fileCount As UInteger = DragQueryFile(wParam, UInteger.MaxValue, Nothing, 0)
+        ReDim dropFilePaths(CInt(fileCount) - 1)
+
+        For i As UInteger = 0 To CInt(fileCount) - 1
+            Dim sb As New StringBuilder(MAX_PATH)
+            Dim result As UInteger = DragQueryFile(wParam, i, sb, sb.Capacity)
+            If result > 0 Then dropFilePaths(i) = sb.ToString()
+        Next
+
+        DragQueryPoint(wParam, dropPoint)
+        DragFinish(wParam)
+        Return True
+    End Function
+
+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.6.0")>
+<Assembly: AssemblyFileVersion("2.10.6.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/MyCompItem.xaml.vb

@@ -97,7 +97,7 @@
         Dim Titles As New List(Of String)
         If FrmMain.PageCurrent.Page = FormMain.PageType.CompDetail Then
             For Each Card As MyCard In FrmDownloadCompDetail.PanResults.Children
-                If Card.Title <> "" AndAlso Not Card.IsSwaped Then Titles.Add(Card.Title)
+                If Card.Title <> "" AndAlso Not Card.IsSwapped Then Titles.Add(Card.Title)
             Next
             Log("[Comp] 记录当前已展开的卡片:" & String.Join("、", Titles))
             FrmMain.PageCurrent.Additional(1) = Titles

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

@@ -34,7 +34,7 @@
                     <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="版本" Margin="0,0,18,0" />
                     <local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" IsEditable="True" MaxDropDownHeight="320">
                         <local:MyComboBoxItem Content="全部 (也可自行输入)" IsSelected="True" />
-                        <local:MyComboBoxItem Content="1.21.5" />
+                        <local:MyComboBoxItem Content="1.21.8" />
                         <local:MyComboBoxItem Content="1.21.1" />
                         <local:MyComboBoxItem Content="1.20.6" />
                         <local:MyComboBoxItem Content="1.20.1" />

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

@@ -68,6 +68,10 @@ Public Class PageComp
     ''' 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。
     ''' </summary>
     Public Shared TargetVersion As McVersion = Nothing
+    ''' <summary>
+    ''' 在切换到该页面时自动设置的搜索框内的内容。
+    ''' </summary>
+    Public Shared TargetName As String = Nothing
 
     '在点击 MyCompItem 时会获取 Loader 的输入,以使资源详情页面可以应用相同的筛选项
     Public Loader As New LoaderTask(Of CompProjectRequest, Integer)("社区资源获取:XXX", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000}
@@ -94,6 +98,10 @@ Public Class PageComp
                 ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge")
             End If
             TargetVersion = Nothing
+            If TargetName IsNot Nothing Then
+                TextSearchName.Text = TargetName
+                TargetName = Nothing
+            End If
             '如果已经完成请求,则重新开始
             If IsLoaderInited Then StartNewSearch()
             ScrollToHome()

+ 14 - 11
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb

@@ -73,11 +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
-        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()
@@ -202,7 +205,7 @@
                    CType(FrmMain.PageCurrent.Additional(1), List(Of String)).Contains(NewCard.Title)) Then
                     MyCard.StackInstall(NewStack, If(PageType = CompType.ModPack, 9, 8), Pair.Key) '9 是安装,8 是另存为
                 Else
-                    NewCard.IsSwaped = True
+                    NewCard.IsSwapped = True
                 End If
                 '增加提示
                 If Pair.Key = "其他版本" Then
@@ -211,7 +214,7 @@
             Next
             '如果只有一张卡片,展开第一张卡片
             If PanResults.Children.Count = 1 Then
-                CType(PanResults.Children(0), MyCard).IsSwaped = False
+                CType(PanResults.Children(0), MyCard).IsSwapped = False
             End If
         Catch ex As Exception
             Log(ex, "可视化工程下载列表出错", LogLevel.Feedback)
@@ -305,7 +308,7 @@
         End Try
     End Sub
     '资源下载;整合包另存为
-    Public Shared CachedFolder As String = Nothing '仅在本次缓存的下载文件夹
+    Public Shared CachedFolder As New Dictionary(Of CompType, String) '仅在本次缓存的下载文件夹
     Public Sub Save_Click(sender As Object, e As EventArgs)
         Dim File As CompFile = If(TypeOf sender Is MyListItem, sender, sender.Parent).Tag
         RunInNewThread(
@@ -356,8 +359,8 @@
                         Return False
                     End Function
                     '获取常规资源默认下载位置
-                    If CachedFolder IsNot Nothing Then
-                        DefaultFolder = CachedFolder
+                    If CachedFolder.ContainsKey(Project.Type) Then
+                        DefaultFolder = CachedFolder(Project.Type)
                         Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}")
                     ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
                         DefaultFolder = $"{McVersionCurrent.PathIndie}{SubFolder}"
@@ -422,7 +425,7 @@
                     If Not Target.Contains("\") Then Return
                     '构造步骤加载器
                     Dim LoaderName As String = Desc & "下载:" & GetFileNameWithoutExtentionFromPath(Target) & " "
-                    If Target <> DefaultFolder AndAlso File.Type = CompType.Mod Then CachedFolder = GetPathFromFullPath(Target)
+                    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 - 1
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb

@@ -88,7 +88,7 @@
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
                 NewCard.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
-                NewCard.IsSwaped = True
+                NewCard.IsSwapped = True
                 PanMain.Children.Add(NewCard)
             Next
         Catch ex As Exception

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

@@ -19,7 +19,7 @@
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Version}
                 NewCard.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
-                NewCard.IsSwaped = True
+                NewCard.IsSwapped = True
                 PanMain.Children.Add(NewCard)
             Next
             ''非官方源警示

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

@@ -27,7 +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: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" IsSwapped="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>
@@ -45,7 +45,7 @@
                                 Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
                         </Grid>
                     </local:MyCard>
-                        <local:MyCard Title="NeoForge" Height="40" Margin="0,12,0,0" x:Name="CardNeoForge" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
+                        <local:MyCard Title="NeoForge" Height="40" Margin="0,12,0,0" x:Name="CardNeoForge" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
                         <StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanNeoForge" />
                         <Grid x:Name="PanNeoForgeInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
@@ -63,7 +63,7 @@
                           Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
                         </Grid>
                     </local:MyCard>
-                        <local:MyCard Title="Fabric" Height="40" Margin="0,12,0,0" x:Name="CardFabric" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
+                        <local:MyCard Title="Fabric" Height="40" Margin="0,12,0,0" x:Name="CardFabric" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
                         <StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanFabric" />
                         <Grid x:Name="PanFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
@@ -81,7 +81,7 @@
                                 Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
                         </Grid>
                     </local:MyCard>
-                        <local:MyCard Title="Fabric API" Height="40" Margin="0,12,0,0" x:Name="CardFabricApi" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
+                        <local:MyCard Title="Fabric API" Height="40" Margin="0,12,0,0" x:Name="CardFabricApi" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
                         <StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanFabricApi" />
                         <Grid x:Name="PanFabricApiInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
@@ -99,7 +99,7 @@
                                 Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
                         </Grid>
                     </local:MyCard>
-                        <local:MyCard Title="OptiFine" Height="40" Margin="0,12,0,0" x:Name="CardOptiFine" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
+                        <local:MyCard Title="OptiFine" Height="40" Margin="0,12,0,0" x:Name="CardOptiFine" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
                         <StackPanel Margin="20,40,18,15" VerticalAlignment="Top">
                             <StackPanel Name="PanOptiFine" />
                         </StackPanel>
@@ -119,7 +119,7 @@
                                 Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
                         </Grid>
                     </local:MyCard>
-                        <local:MyCard Title="OptiFabric" Height="40" Margin="0,12,0,0" x:Name="CardOptiFabric" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
+                        <local:MyCard Title="OptiFabric" Height="40" Margin="0,12,0,0" x:Name="CardOptiFabric" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
                         <StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanOptiFabric" />
                         <Grid x:Name="PanOptiFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
@@ -137,7 +137,7 @@
                                 Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
                         </Grid>
                     </local:MyCard>
-                        <local:MyCard Title="LiteLoader" Height="40" Margin="0,12,0,0" x:Name="CardLiteLoader" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
+                        <local:MyCard Title="LiteLoader" Height="40" Margin="0,12,0,0" x:Name="CardLiteLoader" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
                         <StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanLiteLoader" />
                         <Grid x:Name="PanLiteLoaderInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>

+ 46 - 41
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb

@@ -54,18 +54,13 @@
 
         DisabledPageAnimControls.Remove(BtnStart)
         BtnStart.Show = True
-        CardOptiFine.IsSwaped = True
-        CardLiteLoader.IsSwaped = True
-        CardForge.IsSwaped = True
-        CardNeoForge.IsSwaped = True
-        CardFabric.IsSwaped = True
-        CardFabricApi.IsSwaped = True
-        CardOptiFabric.IsSwaped = True
-
-        If Not Setup.Get("HintInstallBack") Then
-            Setup.Set("HintInstallBack", True)
-            Hint("点击 Minecraft 项即可返回游戏主版本选择页面!")
-        End If
+        CardOptiFine.IsSwapped = True
+        CardLiteLoader.IsSwapped = True
+        CardForge.IsSwapped = True
+        CardNeoForge.IsSwapped = True
+        CardFabric.IsSwapped = True
+        CardFabricApi.IsSwapped = True
+        CardOptiFabric.IsSwapped = True
 
         '如果在选择页面按了刷新键,选择页的东西可能会由于动画被隐藏,但不会由于加载结束而再次显示,因此这里需要手动恢复
         For Each Control In GetAllAnimControls(PanSelect)
@@ -318,8 +313,8 @@
         'OptiFine
         Dim OptiFineError As String = LoadOptiFineGetError()
         CardOptiFine.MainSwap.Visibility = If(OptiFineError Is Nothing, Visibility.Visible, Visibility.Collapsed)
-        If OptiFineError IsNot Nothing Then CardOptiFine.IsSwaped = True '例如在同时展开卡片时选择了不兼容项则强制折叠
-        SetOptiFineInfoShow(CardOptiFine.IsSwaped)
+        If OptiFineError IsNot Nothing Then CardOptiFine.IsSwapped = True '例如在同时展开卡片时选择了不兼容项则强制折叠
+        SetOptiFineInfoShow(CardOptiFine.IsSwapped)
         If SelectedOptiFine Is Nothing Then
             BtnOptiFineClear.Visibility = Visibility.Collapsed
             ImgOptiFine.Visibility = Visibility.Collapsed
@@ -338,8 +333,8 @@
             CardLiteLoader.Visibility = Visibility.Visible
             Dim LiteLoaderError As String = LoadLiteLoaderGetError()
             CardLiteLoader.MainSwap.Visibility = If(LiteLoaderError Is Nothing, Visibility.Visible, Visibility.Collapsed)
-            If LiteLoaderError IsNot Nothing Then CardLiteLoader.IsSwaped = True '例如在同时展开卡片时选择了不兼容项则强制折叠
-            SetLiteLoaderInfoShow(CardLiteLoader.IsSwaped)
+            If LiteLoaderError IsNot Nothing Then CardLiteLoader.IsSwapped = True '例如在同时展开卡片时选择了不兼容项则强制折叠
+            SetLiteLoaderInfoShow(CardLiteLoader.IsSwapped)
             If SelectedLiteLoader Is Nothing Then
                 BtnLiteLoaderClear.Visibility = Visibility.Collapsed
                 ImgLiteLoader.Visibility = Visibility.Collapsed
@@ -355,8 +350,8 @@
         'Forge
         Dim ForgeError As String = LoadForgeGetError()
         CardForge.MainSwap.Visibility = If(ForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed)
-        If ForgeError IsNot Nothing Then CardForge.IsSwaped = True
-        SetForgeInfoShow(CardForge.IsSwaped)
+        If ForgeError IsNot Nothing Then CardForge.IsSwapped = True
+        SetForgeInfoShow(CardForge.IsSwapped)
         If SelectedForge Is Nothing Then
             BtnForgeClear.Visibility = Visibility.Collapsed
             ImgForge.Visibility = Visibility.Collapsed
@@ -375,8 +370,8 @@
             CardNeoForge.Visibility = Visibility.Visible
             Dim NeoForgeError As String = LoadNeoForgeGetError()
             CardNeoForge.MainSwap.Visibility = If(NeoForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed)
-            If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwaped = True
-            SetNeoForgeInfoShow(CardNeoForge.IsSwaped)
+            If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwapped = True
+            SetNeoForgeInfoShow(CardNeoForge.IsSwapped)
             If SelectedNeoForge Is Nothing Then
                 BtnNeoForgeClear.Visibility = Visibility.Collapsed
                 ImgNeoForge.Visibility = Visibility.Collapsed
@@ -396,8 +391,8 @@
             CardFabric.Visibility = Visibility.Visible
             Dim FabricError As String = LoadFabricGetError()
             CardFabric.MainSwap.Visibility = If(FabricError Is Nothing, Visibility.Visible, Visibility.Collapsed)
-            If FabricError IsNot Nothing Then CardFabric.IsSwaped = True
-            SetFabricInfoShow(CardFabric.IsSwaped)
+            If FabricError IsNot Nothing Then CardFabric.IsSwapped = True
+            SetFabricInfoShow(CardFabric.IsSwapped)
             If SelectedFabric Is Nothing Then
                 BtnFabricClear.Visibility = Visibility.Collapsed
                 ImgFabric.Visibility = Visibility.Collapsed
@@ -417,8 +412,8 @@
             CardFabricApi.Visibility = Visibility.Visible
             Dim FabricApiError As String = LoadFabricApiGetError()
             CardFabricApi.MainSwap.Visibility = If(FabricApiError Is Nothing, Visibility.Visible, Visibility.Collapsed)
-            If FabricApiError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardFabricApi.IsSwaped = True
-            SetFabricApiInfoShow(CardFabricApi.IsSwaped)
+            If FabricApiError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardFabricApi.IsSwapped = True
+            SetFabricApiInfoShow(CardFabricApi.IsSwapped)
             If SelectedFabricApi Is Nothing Then
                 BtnFabricApiClear.Visibility = Visibility.Collapsed
                 ImgFabricApi.Visibility = Visibility.Collapsed
@@ -438,8 +433,8 @@
             CardOptiFabric.Visibility = Visibility.Visible
             Dim OptiFabricError As String = LoadOptiFabricGetError()
             CardOptiFabric.MainSwap.Visibility = If(OptiFabricError Is Nothing, Visibility.Visible, Visibility.Collapsed)
-            If OptiFabricError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardOptiFabric.IsSwaped = True
-            SetOptiFabricInfoShow(CardOptiFabric.IsSwaped)
+            If OptiFabricError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardOptiFabric.IsSwapped = True
+            SetOptiFabricInfoShow(CardOptiFabric.IsSwapped)
             If SelectedOptiFabric Is Nothing Then
                 BtnOptiFabricClear.Visibility = Visibility.Collapsed
                 ImgOptiFabric.Visibility = Visibility.Collapsed
@@ -639,7 +634,7 @@
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
                 NewCard.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
-                NewCard.IsSwaped = True
+                NewCard.IsSwapped = True
                 PanMinecraft.Children.Add(NewCard)
             Next
             '自动选择版本
@@ -676,6 +671,11 @@
             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
@@ -750,14 +750,14 @@
         OptiFabric_Loaded()
         Forge_Loaded()
         NeoForge_Loaded()
-        CardOptiFine.IsSwaped = True
+        CardOptiFine.IsSwapped = True
         SelectReload()
     End Sub
     Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp
         SelectedOptiFine = Nothing
         SelectedOptiFabric = Nothing
         AutoSelectedOptiFabric = False
-        CardOptiFine.IsSwaped = True
+        CardOptiFine.IsSwapped = True
         e.Handled = True
         Forge_Loaded()
         NeoForge_Loaded()
@@ -811,12 +811,12 @@
     '选择与清除
     Private Sub LiteLoader_Selected(sender As MyListItem, e As EventArgs)
         SelectedLiteLoader = sender.Tag
-        CardLiteLoader.IsSwaped = True
+        CardLiteLoader.IsSwapped = True
         SelectReload()
     End Sub
     Private Sub LiteLoader_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnLiteLoaderClear.MouseLeftButtonUp
         SelectedLiteLoader = Nothing
-        CardLiteLoader.IsSwaped = True
+        CardLiteLoader.IsSwapped = True
         e.Handled = True
         SelectReload()
     End Sub
@@ -897,14 +897,14 @@
     '选择与清除
     Private Sub Forge_Selected(sender As MyListItem, e As EventArgs)
         SelectedForge = sender.Tag
-        CardForge.IsSwaped = True
+        CardForge.IsSwapped = True
         If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, SelectedForge) Then SelectedOptiFine = Nothing
         OptiFine_Loaded()
         SelectReload()
     End Sub
     Private Sub Forge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnForgeClear.MouseLeftButtonUp
         SelectedForge = Nothing
-        CardForge.IsSwaped = True
+        CardForge.IsSwapped = True
         e.Handled = True
         OptiFine_Loaded()
         SelectReload()
@@ -959,13 +959,13 @@
     '选择与清除
     Private Sub NeoForge_Selected(sender As MyListItem, e As EventArgs)
         SelectedNeoForge = sender.Tag
-        CardNeoForge.IsSwaped = True
+        CardNeoForge.IsSwapped = True
         OptiFine_Loaded()
         SelectReload()
     End Sub
     Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp
         SelectedNeoForge = Nothing
-        CardNeoForge.IsSwaped = True
+        CardNeoForge.IsSwapped = True
         e.Handled = True
         OptiFine_Loaded()
         SelectReload()
@@ -981,6 +981,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
+        '检查 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 不兼容"
@@ -1020,7 +1025,7 @@
         SelectedFabric = sender.Tag("version").ToString
         FabricApi_Loaded()
         OptiFabric_Loaded()
-        CardFabric.IsSwaped = True
+        CardFabric.IsSwapped = True
         SelectReload()
     End Sub
     Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp
@@ -1029,7 +1034,7 @@
         AutoSelectedFabricApi = False
         SelectedOptiFabric = Nothing
         AutoSelectedOptiFabric = False
-        CardFabric.IsSwaped = True
+        CardFabric.IsSwapped = True
         e.Handled = True
         SelectReload()
     End Sub
@@ -1141,12 +1146,12 @@
     '选择与清除
     Private Sub FabricApi_Selected(sender As MyListItem, e As EventArgs)
         SelectedFabricApi = sender.Tag
-        CardFabricApi.IsSwaped = True
+        CardFabricApi.IsSwapped = True
         SelectReload()
     End Sub
     Private Sub FabricApi_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricApiClear.MouseLeftButtonUp
         SelectedFabricApi = Nothing
-        CardFabricApi.IsSwaped = True
+        CardFabricApi.IsSwapped = True
         e.Handled = True
         SelectReload()
     End Sub
@@ -1233,12 +1238,12 @@
     '选择与清除
     Private Sub OptiFabric_Selected(sender As MyListItem, e As EventArgs)
         SelectedOptiFabric = sender.Tag
-        CardOptiFabric.IsSwaped = True
+        CardOptiFabric.IsSwapped = True
         SelectReload()
     End Sub
     Private Sub OptiFabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFabricClear.MouseLeftButtonUp
         SelectedOptiFabric = Nothing
-        CardOptiFabric.IsSwaped = True
+        CardOptiFabric.IsSwapped = True
         e.Handled = True
         SelectReload()
     End Sub

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

@@ -34,7 +34,7 @@
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
                 NewCard.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
-                NewCard.IsSwaped = True
+                NewCard.IsSwapped = True
                 PanMain.Children.Add(NewCard)
             Next
         Catch ex As Exception

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

@@ -23,7 +23,7 @@
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
                 NewCard.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
-                NewCard.IsSwaped = True
+                NewCard.IsSwapped = True
                 PanMain.Children.Add(NewCard)
             Next
         Catch ex As Exception

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

@@ -38,7 +38,7 @@
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
                 NewCard.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
-                NewCard.IsSwaped = True
+                NewCard.IsSwapped = True
                 PanMain.Children.Add(NewCard)
             Next
         Catch ex As Exception

+ 34 - 23
Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb

@@ -87,7 +87,7 @@
         LabTitle.Text = "登录 Minecraft"
         LabCaption.Text =
             $"登录网页将自动开启,请在网页中输入 {UserCode}(已自动复制)。" & vbCrLf & vbCrLf &
-            $"如果网络环境不佳,网页可能一直加载不出来,届时请使用使用加速器或 VPN 以改善网络环境。" & vbCrLf &
+            $"如果网络环境不佳,网页可能一直加载不出来,届时请使用加速器或 VPN 以改善网络环境。" & vbCrLf &
             $"你也可以用其他设备打开 {Website} 并输入上述代码。"
         Btn1.EventData = Website
         Btn2.EventData = UserCode
@@ -105,13 +105,16 @@
         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,
+                    MakeLog:=False)
                 '获取结果
                 Dim ResultJson As JObject = GetJson(Result)
                 McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒")
@@ -119,21 +122,29 @@
                 Finished({ResultJson("access_token").ToString, ResultJson("refresh_token").ToString})
                 Return
             Catch ex As Exception
-                If ex.Message.Contains("authorization_declined") Then
-                    Finished(New Exception("$你拒绝了 PCL 申请的权限……"))
-                    Return
-                ElseIf ex.Message.Contains("expired_token") Then
-                    Finished(New Exception("$登录用时太长啦,重新试试吧!"))
-                    Return
-                ElseIf ex.Message.Contains("service abuse") Then
-                    Finished(New Exception("$非常抱歉,该账号已被微软封禁,无法登录。"))
-                    Return
-                ElseIf ex.Message.Contains("AADSTS70000") Then '可能不能判 “invalid_grant”,见 #269
-                    Finished(New RestartException)
-                    Return
-                ElseIf ex.Message.Contains("authorization_pending") Then
-                    Thread.Sleep(2000)
-                ElseIf UnknownFailureCount <= 2 Then
+                If TypeOf ex Is ResponsedWebException Then
+                    Dim Response = CType(ex, ResponsedWebException).Response
+                    If Response.Contains("authorization_declined") Then
+                        Finished(New Exception("$你拒绝了 PCL 申请的权限……"))
+                        Return
+                    ElseIf Response.Contains("expired_token") Then
+                        Finished(New Exception("$登录用时太长啦,重新试试吧!"))
+                        Return
+                    ElseIf Response.Contains("Account security interrupt") Then
+                        Finished(New Exception("$该账号由于安全问题无法登陆,请前往微软账户页获取更多信息。"))
+                        Return
+                    ElseIf Response.Contains("service abuse") Then
+                        Finished(New Exception("$非常抱歉,该账号已被微软封禁,无法登录。"))
+                        Return
+                    ElseIf Response.Contains("AADSTS70000") Then '可能不能判 “invalid_grant”,见 #269
+                        Finished(New RestartException)
+                        Return
+                    ElseIf Response.Contains("authorization_pending") Then
+                        Thread.Sleep(2000)
+                        Continue Do
+                    End If
+                End If
+                If UnknownFailureCount <= 2 Then
                     UnknownFailureCount += 1
                     Log(ex, $"登录轮询第 {UnknownFailureCount} 次失败")
                     Thread.Sleep(2000)

+ 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)

+ 15 - 13
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb

@@ -243,19 +243,21 @@
             If Not IsNothing(PageNew) AndAlso Not IsNothing(PageNew.Parent) Then PageNew.SetValue(ContentPresenter.ContentProperty, Nothing)
             If Anim Then
                 '动画
-                Dispatcher.Invoke(Sub()
-                                      '执行动画
-                                      AniStart({
-                                                     AaOpacity(PanLogin, -PanLogin.Opacity, 100,, New AniEaseOutFluent),
-                                                     AaCode(Sub()
-                                                                AniControlEnabled += 1
-                                                                PanLogin.Children.Clear()
-                                                                PanLogin.Children.Add(PageNew)
-                                                                AniControlEnabled -= 1
-                                                            End Sub, 100),
-                                                     AaOpacity(PanLogin, 1, 100, 120, New AniEaseInFluent)
-                                                 }, "FrmLogin PageChange")
-                                  End Sub, Threading.DispatcherPriority.Render)
+                Dispatcher.Invoke(
+                Sub()
+                    '执行动画
+                    AniStart({
+                        AaOpacity(PanLogin, -PanLogin.Opacity, 100,, New AniEaseOutFluent),
+                        AaCode(
+                        Sub()
+                            AniControlEnabled += 1
+                            PanLogin.Children.Clear()
+                            PanLogin.Children.Add(PageNew)
+                            AniControlEnabled -= 1
+                        End Sub, 100),
+                        AaOpacity(PanLogin, 1, 100, 120, New AniEaseInFluent)
+                    }, "FrmLogin PageChange")
+                End Sub, Threading.DispatcherPriority.Render)
             Else
                 '无动画
                 AniControlEnabled += 1

+ 8 - 6
Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb

@@ -88,11 +88,11 @@ Download:
                             </local:MyCard>"
                     Case 2
                         Log("[Page] 主页预设:Minecraft 新闻")
-                        Url = "http://pcl.mcnews.thestack.top"
+                        Url = "https://pcl.mcnews.thestack.top"
                         GoTo Download
                     Case 3
                         Log("[Page] 主页预设:简单主页")
-                        Url = "https://raw.gitcode.com/mfn233/PCL-Mainpage/raw/main/Custom.xaml"
+                        Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/MFn233/Custom.xaml"
                         GoTo Download
                     Case 4
                         Log("[Page] 主页预设:每日整合包推荐")
@@ -128,7 +128,7 @@ Download:
                         GoTo Download
                     Case 12
                         Log("[Page] 主页预设:PCL GitHub 仪表盘")
-                        Url = "https://raw.gitcode.com/Deep-Dark-Forest/PCL2-GitHub-Dashboard-Homepage/raw/main/custom.xaml"
+                        Url = "https://ddf.pcl-community.org/Custom.xaml"
                         GoTo Download
                 End Select
         End Select
@@ -155,7 +155,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
@@ -170,7 +170,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)
@@ -230,7 +230,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

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

@@ -23,10 +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
                 '获取新皮肤地址

+ 252 - 268
Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml

@@ -17,7 +17,7 @@
                         <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="龙腾猫跃" 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>
@@ -29,7 +29,7 @@
                             <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="赞助 PCL !" Grid.Column="2" Grid.Row="0" Height="35" EventType="打开网页" EventData="https://afdian.com/a/LTCat" ColorType="Highlight" />
                     <local:MyButton Text="查看源代码" Grid.Column="2" Grid.Row="1" Height="35" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2" />
                 </Grid>
             </local:MyCard>
@@ -51,11 +51,12 @@
                         <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:MyButton Text="赞助镜像源" Grid.Row="0" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://afdian.com/a/bangbang93" />
                     <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:MyButton Text="打开百科" Grid.Row="1" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://www.mcmod.cn" />
+                    <local:MyListItem Title="z0z0r4" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 MCIM 社区资源镜像源和帮助库图床!" Grid.Row="2" Grid.Column="1" />
+                    <local:MyButton Text="MCIM 主页" Grid.Row="2" Grid.Column="2" Height="35" EventType="打开网页" EventData="https://github.com/mcmod-info-mirror" />
+                    <local:MyListItem Title="00ll00" Margin="-5,0,0,0" IsHitTestVisible="False" Grid.ColumnSpan="2" Info="提供了 Java Launch Wrapper 和一些重要服务支持!" 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" />
@@ -70,12 +71,12 @@
                             <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 Source="/Plain Craft Launcher 2;component/Images/Heads/z0z0r4.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 Source="/Plain Craft Launcher 2;component/Images/Heads/00ll00.png" Grid.Row="3" Margin="3">
                         <Image.Clip>
                             <EllipseGeometry Center="17 17" RadiusX="17" RadiusY="17" />
                         </Image.Clip>
@@ -104,265 +105,246 @@
             </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" />
+                    <TextBlock HorizontalAlignment="Left" Text="以下是 2025 年 7 月的高级赞助者们!感谢各位对 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="ZSF" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TizhleF" />
+                        <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="Flying___pig" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="绝世" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Jwac" />
+                        <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="Cess" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ender_glass" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TT" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="白玄.雪狼" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="MUCT" />
+                        <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="Ander·Les" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Eoctopus" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="时光飞逝一下午" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Carl7412" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ShinyChariot" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="骨故谷" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="?" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Jinji希子" />
+                        <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="TBONC" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="祈农" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="凤凰院凶真" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ShuangDaoRen" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="PXZ233" />
+                        <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="66112447" />
+                        <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="Pari~" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="红云" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Link-an" />
+                        <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="liuxuan" />
+                        <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="S0D1UM" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="吞仔送命" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="白浪宇宙" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Crimson" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Mibo_MkIX" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="G.snine18^7" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="两把雨伞" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="古明地苑" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="MarbleMionshevic" />
+                        <TextBlock Width="120" Margin="0,1.5" Text=":〈" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="紫罗兰" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="asai" />
+                        <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="XunWei" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="sirous" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="路过某打酱油的" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Gnight07" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="结构德古拉" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="myyukee" />
+                        <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="YYC" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="晨曦酒庄高冷男" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="herald112" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Foxman" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KoreQAQ" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TechOfRevo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="lqs" />
+                        <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="全明星小面包批发商DIO" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="碎序" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="U" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Dumlet" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="johnqian" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KANG" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="owod" />
+                        <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="UA-Ming" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="可爱小探姬" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="霜月" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="bud" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="小虐SAIKYO" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zealotand" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="comico" />
+                        <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="LOG175" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="绵" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="scp00813" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="宝宝" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="9527" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ICE_xwx" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="羽笙喵喵" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ssvgg(☉ x ☉)" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Happymeals" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="shen12244" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="一般路过AAA" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Niko" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="qroiwopqri" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TringDron" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="有屳則名" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="TX2006" />
+                        <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="wyh114514" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="飞飞在睡觉。" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="柚西" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="XIAOSHUAI" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="saxioh" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="baibaibai" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="事屑Jerry" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Rs32" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="SPDish" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="风早九夜" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Reg" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="hellord" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="时本ki" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="mamana" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="放肆的微笑" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="人没逝" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="wddjww" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="xiao澄" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="银狼碎片" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="OniKata_Kayoko" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="曦" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Echo" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="画卿颜" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="夏 " />
+                        <TextBlock Width="120" Margin="0,1.5" Text="jiege45" />
+                        <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="qwqewe" />
                         <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="YJ_42" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Xricron" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="抛弃大脑" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="10455885" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="羽渡白砂" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="沙" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="sukous" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="册那" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KMYLS" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="kakadeso" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="懒猫" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="清岚" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Atopos" />
+                        <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="H1saNe" />
+                        <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="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" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Landsilde" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zzz" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="星期三" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Ashburn" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="123" />
+                        <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="郁子恒是FW" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="每天吃吃吃吃吃不饱" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Freezhao" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="ZHypocrite" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="十下小雨" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="九 十" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="RuthlessXdream" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="zv-vz" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="汐沄的随记" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="REMOTE" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="昔阳" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="七度ユキノ" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KNISX" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="RainbowDashRD" />
+                        <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="四分白Fortenz" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="UUZ" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Ankairis" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Jn1E" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="云山如昨好" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="LYJ0415" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="空?" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="Awfrlu+" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="懒帝" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="北安丶七叔" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="xkw" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="KINO" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="POTATO" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="少昊氏" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="狂踹瘸子那條好腿丶" />
+                        <TextBlock Width="240" Margin="0,1.5" Text="Thallium_Ash【铊尘】(坨坨)" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="陌雨泠涯" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="bajie79" />
+                        <TextBlock Width="120" Margin="0,1.5" Text="紘垰" />
                     </WrapPanel>
                     <Grid Height="35">
                         <Grid.ColumnDefinitions>
@@ -377,11 +359,13 @@
                     </Grid>
                 </StackPanel>
             </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="法律信息" IsSwaped="True" CanSwap="True">
+            <local:MyCard Margin="0,0,0,15" Title="法律信息" IsSwapped="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 Text="匿名数据收集" TextWrapping="Wrap" Margin="0,6,0,4" FontWeight="Bold" />
+                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="当下载失败、PCL 出错、游戏崩溃等事件时,PCL 会匿名上报信息,以通知龙猫 “哦吼,出错了”,让龙猫能更好地修 Bug……(PCL 不会上传或收集任何个人信息,包括上报的东西里面也没有!)&#xa;你可以在 设置 → 其他 → 启动器 中关闭上报,不过龙猫就不知道你遇到了什么 Bug 了……" TextWrapping="Wrap" />
+                    <TextBlock Text="隐私声明与个人信息保护政策" TextWrapping="Wrap" Margin="0,10,0,4" FontWeight="Bold" />
+                    <TextBlock Margin="0,0,0,2" LineHeight="17" Text="PCL 没有收集个人信息,所以这里其实啥都不用写……&#xa;留个这个其实只是为了让你知道 PCL 没有收集个人信息,诶嘿。" TextWrapping="Wrap" />
+                    <TextBlock Text="其他信息" TextWrapping="Wrap" Margin="0,10,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" />
@@ -389,7 +373,7 @@
                     </StackPanel>
                 </StackPanel>
             </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="许可与版权声明" IsSwaped="True" CanSwap="True">
+            <local:MyCard Margin="0,0,0,15" Title="许可与版权声明" IsSwapped="True" CanSwap="True">
                 <Grid Margin="25,42,23,4">
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="Auto" />

+ 0 - 6
Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb

@@ -18,12 +18,6 @@
 
     End Sub
 
-    Private Sub BtnAboutBmclapi_Click(sender As Object, e As EventArgs) Handles BtnAboutBmclapi.Click
-        OpenWebsite("https://afdian.com/a/bangbang93")
-    End Sub
-    Private Sub BtnAboutWiki_Click(sender As Object, e As EventArgs) Handles BtnAboutWiki.Click
-        OpenWebsite("https://www.mcmod.cn")
-    End Sub
     Public Shared Sub CopyUniqueAddress() Handles BtnDonateCopy.Click
         ClipboardSet(UniqueAddress)
     End Sub

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

@@ -55,7 +55,7 @@
                 If Type = "指南" Then
                     MyCard.StackInstall(NewStack, 11, "指南")
                 Else
-                    NewCard.IsSwaped = True
+                    NewCard.IsSwapped = True
                 End If
                 PanList.Children.Add(NewCard)
             Next

+ 2 - 2
Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb

@@ -137,7 +137,7 @@
             Case 1
                 Feedback(True, False)
             Case 2
-                OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/")
+                OpenWebsite("https://github.com/Meloong-Git/PCL/issues/")
         End Select
     End Sub
     Private Sub TryVote(sender As Object, e As RouteEventArgs) Handles ItemVote.Changed
@@ -148,7 +148,7 @@
     Public Shared Sub TryVote()
         If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请使用 VPN 以改善网络环境。",
                     "新功能投票", "打开", "取消") = 2 Then Return
-        OpenWebsite("https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created")
+        OpenWebsite("https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created")
     End Sub
 
 End Class

+ 3 - 0
Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb

@@ -317,6 +317,9 @@
     Public Sub Delete_Click(sender As Object, e As RoutedEventArgs)
         Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag
         Dim DeleteText As String = If((Folder.Type = McFolderType.Original OrElse Folder.Type = McFolderType.RenamedOriginal) AndAlso Folder.Path = Path & ".minecraft\" AndAlso McFolderList.Count = 1, "清空", "删除")
+        If Folder.Path.EndsWith(":\") OrElse Folder.Path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\" OrElse Folder.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\" Then '#7030
+            If MyMsgBox("将要删除的游戏文件夹是系统关键目录(" & Folder.Path & "),确定要删除吗?", "删除警告", "取消", "确认", "取消", IsWarn:=True) <> 2 Then Return
+        End If
         If MyMsgBox("你确定要" & DeleteText & "这个文件夹吗?" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这会导致该文件夹中的所有存档与其他文件永久丢失,且不可恢复!", "删除警告", "取消", "确认", "取消") <> 2 Then Return
         If MyMsgBox("如果你在该文件夹中存放了除 MC 以外的其他文件,这些文件也会被一同删除!" & vbCrLf & "继续删除会导致该文件夹中的所有文件永久丢失,请在仔细确认后再继续!" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这是最后一次警告!", "删除警告", "确认" & DeleteText, "取消", IsWarn:=True) <> 1 Then Return
         '移出列表

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

@@ -81,15 +81,15 @@
                 PanMain.Children.Add(NewCard)
                 '确定卡片是否展开
                 If Card.Key = McVersionCardType.Rubbish OrElse Card.Key = McVersionCardType.Error OrElse Card.Key = McVersionCardType.Fool Then
-                    NewCard.IsSwaped = True
+                    NewCard.IsSwapped = True
                 Else
                     MyCard.StackInstall(NewStack, 0, CardTitle)
                 End If
             Next
 
             '若只有一个卡片,则强制展开
-            If PanMain.Children.Count = 1 AndAlso CType(PanMain.Children(0), MyCard).IsSwaped Then
-                CType(PanMain.Children(0), MyCard).IsSwaped = False
+            If PanMain.Children.Count = 1 AndAlso CType(PanMain.Children(0), MyCard).IsSwapped Then
+                CType(PanMain.Children(0), MyCard).IsSwapped = False
             End If
 
             '判断应该显示哪一个页面
@@ -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

+ 4 - 3
Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb

@@ -14,7 +14,6 @@
         {"HintDownloadThread", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"HintNotice", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"HintDownload", New SetupEntry(0, Source:=SetupSource.Registry)},
-        {"HintInstallBack", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"HintHide", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"HintHandInstall", New SetupEntry(False, Source:=SetupSource.Registry)},
         {"HintBuy", New SetupEntry(False, Source:=SetupSource.Registry)},
@@ -42,6 +41,7 @@
         {"SystemSystemCache", New SetupEntry("", Source:=SetupSource.Registry)},
         {"SystemSystemUpdate", New SetupEntry(0)},
         {"SystemSystemActivity", New SetupEntry(0)},
+        {"SystemSystemTelemetry", New SetupEntry(True, Source:=SetupSource.Registry)},
         {"CacheExportConfig", New SetupEntry("", Source:=SetupSource.Registry)},
         {"CacheSavedPageUrl", New SetupEntry("", Source:=SetupSource.Registry)},
         {"CacheSavedPageVersion", New SetupEntry("", Source:=SetupSource.Registry)},
@@ -124,7 +124,7 @@
         {"ToolDownloadTranslateV2", New SetupEntry(1, Source:=SetupSource.Registry)},
         {"ToolDownloadIgnoreQuilt", New SetupEntry(True, Source:=SetupSource.Registry)},
         {"ToolDownloadCert", New SetupEntry(False, Source:=SetupSource.Registry)},
-        {"ToolDownloadMod", New SetupEntry(1, Source:=SetupSource.Registry)},
+        {"ToolDownloadMod", New SetupEntry(2, Source:=SetupSource.Registry)},
         {"ToolModLocalNameStyle", New SetupEntry(0, Source:=SetupSource.Registry)},
         {"ToolUpdateAlpha", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
         {"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)},
@@ -181,6 +181,7 @@
         {"VersionAdvanceRun", New SetupEntry("", Source:=SetupSource.Version)},
         {"VersionAdvanceRunWait", New SetupEntry(True, Source:=SetupSource.Version)},
         {"VersionAdvanceDisableJLW", New SetupEntry(False, Source:=SetupSource.Version)},
+        {"VersionAdvanceDisableModUpdate", New SetupEntry(False, Source:=SetupSource.Version)},
         {"VersionRamType", New SetupEntry(2, Source:=SetupSource.Version)},
         {"VersionRamCustom", New SetupEntry(15, Source:=SetupSource.Version)},
         {"VersionRamOptimize", New SetupEntry(0, Source:=SetupSource.Version)},
@@ -610,7 +611,7 @@
                 FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible)
                 FrmSetupUI.HintCustom.Text = $"从指定网址联网获取主页内容。服主也可以用于动态更新服务器公告。{vbCrLf}如果你制作了稳定运行的联网主页,可以点击这条提示投稿,若合格即可加入预设!"
                 FrmSetupUI.HintCustom.EventType = "打开网页"
-                FrmSetupUI.HintCustom.EventData = "https://github.com/Hex-Dragon/PCL2/discussions/2528"
+                FrmSetupUI.HintCustom.EventData = "https://github.com/Meloong-Git/PCL/discussions/2528"
             Case 3 '预设
                 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Visible
                 FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed

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

@@ -171,7 +171,7 @@
                     </Grid>
                 </StackPanel>
             </local:MyCard>
-            <local:MyCard x:Name="CardSkin" Margin="0,0,0,15" Title="离线皮肤" CanSwap="True" IsSwaped="True">
+            <local:MyCard x:Name="CardSkin" Margin="0,0,0,15" Title="离线皮肤" CanSwap="True" IsSwapped="True">
                 <StackPanel Margin="25,40,25,15">
                     <local:MyHint Text="由于技术问题,此功能只保证对 1.19.2 以前的版本有效!" Theme="Yellow" Margin="0,0,0,10" />
                     <Grid Margin="-1,0,0,0">
@@ -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">
@@ -218,7 +218,7 @@
                     </Grid>
                 </StackPanel>
             </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="高级启动选项" IsSwaped="True" CanSwap="True">
+            <local:MyCard Margin="0,0,0,15" Title="高级启动选项" IsSwapped="True" CanSwap="True">
                 <Grid Margin="25,40,25,15">
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="Auto" />
@@ -235,15 +235,15 @@
                         <RowDefinition Height="Auto" />
                     </Grid.RowDefinitions>
                     <StackPanel VerticalAlignment="Center" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,25,0">
-                        <TextBlock Text="JVM 参数头部" VerticalAlignment="Center" />
+                        <TextBlock Text="Java 虚拟机参数" VerticalAlignment="Center" />
                         <local:MyIconButton Height="23" Margin="0,1,0,0" x:Name="BtnAdvanceJvmReset" VerticalAlignment="Center" 
                                         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 则会以试玩模式启动游戏。" />
+                    <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 则会以试玩模式启动游戏。&#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 文件夹。" />

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

@@ -62,16 +62,18 @@
                             <RowDefinition Height="7" />
                             <RowDefinition Height="28" />
                             <RowDefinition Height="7" />
+                            <RowDefinition Height="28" />
+                            <RowDefinition Height="7" />
                             <RowDefinition Height="Auto" />
                         </Grid.RowDefinitions>
-                        <!--<TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="下载源" Margin="0,0,25,0" />
+                        <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="来源" Margin="0,0,25,0" />
                         <local:MyComboBox x:Name="ComboDownloadMod" Grid.Row="0" Grid.ColumnSpan="2" Tag="ToolDownloadMod" Grid.Column="1">
-                            <local:MyComboBoxItem Content="尽量使用镜像源(暂时无效)" />
+                            <local:MyComboBoxItem Content="尽量使用镜像源(可能缺少刚刚更新的版本)" />
                             <local:MyComboBoxItem Content="仅在官方源加载缓慢时改用镜像源" />
                             <local:MyComboBoxItem Content="尽量使用官方源" IsSelected="True" />
-                        </local:MyComboBox>-->
-                        <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="文件名格式" Margin="0,0,25,0" />
-                        <local:MyComboBox Grid.Row="0" x:Name="ComboDownloadTranslateV2" Grid.ColumnSpan="2" Tag="ToolDownloadTranslateV2" Grid.Column="1" 
+                        </local:MyComboBox>
+                        <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="文件名格式" Margin="0,0,25,0" />
+                        <local:MyComboBox Grid.Row="2" x:Name="ComboDownloadTranslateV2" Grid.ColumnSpan="2" Tag="ToolDownloadTranslateV2" Grid.Column="1" 
                                           ToolTip="下载社区资源时,其默认文件名的格式">
                             <local:MyComboBoxItem Content="【机械动力】create-1.21.1-6.0.4" />
                             <local:MyComboBoxItem Content="[机械动力] create-1.21.1-6.0.4" IsSelected="True" />
@@ -79,13 +81,13 @@
                             <local:MyComboBoxItem Content="create-1.21.1-6.0.4-机械动力" />
                             <local:MyComboBoxItem Content="create-1.21.1-6.0.4" />
                         </local:MyComboBox>
-                        <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Mod 管理样式" Margin="0,0,25,0" />
-                        <local:MyComboBox Grid.Row="2" x:Name="ComboModLocalNameStyle" Grid.ColumnSpan="2" Tag="ToolModLocalNameStyle" Grid.Column="1" 
+                        <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Mod 管理样式" Margin="0,0,25,0" />
+                        <local:MyComboBox Grid.Row="4" x:Name="ComboModLocalNameStyle" Grid.ColumnSpan="2" Tag="ToolModLocalNameStyle" Grid.Column="1" 
                                           ToolTip="在 Mod 管理页面中,Mod 项的显示方式">
                             <local:MyComboBoxItem Content="标题显示译名,详情显示文件名" IsSelected="True" />
                             <local:MyComboBoxItem Content="标题显示文件名,详情显示译名" />
                         </local:MyComboBox>
-                        <local:MyCheckBox Margin="0,2,0,4" Text="在显示 Mod 加载器时忽略 Quilt" Grid.Row="4" Height="22" Grid.ColumnSpan="2"
+                        <local:MyCheckBox Margin="0,2,0,4" Text="在显示 Mod 加载器时忽略 Quilt" Grid.Row="6" Height="22" Grid.ColumnSpan="2"
                                           x:Name="CheckDownloadIgnoreQuilt" Tag="ToolDownloadIgnoreQuilt" />
                     </Grid>
                 </StackPanel>
@@ -109,7 +111,7 @@
                     <local:MyCheckBox Text="自动设置为中文" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="5" x:Name="CheckHelpChinese" Tag="ToolHelpChinese" />
                 </Grid>
             </local:MyCard>
-            <local:MyCard Margin="0,0,0,15" Title="系统">
+            <local:MyCard Margin="0,0,0,15" Title="启动器">
                 <Grid Margin="25,40,25,20">
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
@@ -124,6 +126,7 @@
                         <RowDefinition Height="5" />
                         <RowDefinition Height="Auto" />
                         <RowDefinition Height="Auto" />
+                        <RowDefinition Height="Auto" />
                     </Grid.RowDefinitions>
                     <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动器更新" Margin="0,0,25,0" />
                     <local:MyComboBox x:Name="ComboSystemUpdate" Tag="SystemSystemUpdate" Grid.Column="1">
@@ -145,7 +148,10 @@
                             <local:ValidateFolderPath UseMinecraftCharCheck="False" />
                         </local:MyTextBox.ValidateRules>
                     </local:MyTextBox>
-                    <Grid Height="35" Grid.Row="6" Grid.ColumnSpan="5" Margin="0,12,0,0">
+                    <local:MyCheckBox Margin="0,5,0,4" Text="匿名数据收集" Grid.Row="6" Height="22" Grid.ColumnSpan="2"
+                        x:Name="CheckSystemTelemetry" Tag="SystemSystemTelemetry" 
+                        ToolTip="当下载失败、PCL 出错、游戏崩溃等事件时,PCL 会匿名上报情况,以通知龙猫 “哦吼,出错了”,让龙猫能更好地修 Bug……&#xa;你可以关闭上报,不过龙猫就不知道你遇到了什么 Bug 了……&#13;PCL 不会上传或收集任何个人信息,包括上报的东西里面也没有!" />
+                    <Grid Height="35" Grid.Row="7" Grid.ColumnSpan="5" Margin="0,12,0,0">
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
@@ -155,7 +161,7 @@
                         <local:MyButton Grid.Column="1" x:Name="BtnSystemSettingExp" MinWidth="140" Text="导出设置" Padding="13,0" Margin="0,0,20,0" />
                         <local:MyButton Grid.Column="2" x:Name="BtnSystemSettingImp" MinWidth="140" Text="导入设置" Padding="13,0" Margin="0,0,20,0" />
                     </Grid>
-                    <Grid Height="35" Grid.Row="7" Grid.ColumnSpan="5" Margin="0,12,0,0" x:Name="PanDonate">
+                    <Grid Height="35" Grid.Row="8" Grid.ColumnSpan="5" Margin="0,12,0,0" x:Name="PanDonate">
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
@@ -182,7 +188,7 @@
                     </Grid>
                 </StackPanel>
             </local:MyCard> -->
-            <local:MyCard x:Name="CardDebug" Margin="0,0,0,15" Title="调试选项" CanSwap="True" IsSwaped="True">
+            <local:MyCard x:Name="CardDebug" Margin="0,0,0,15" Title="调试选项" CanSwap="True" IsSwapped="True">
                 <StackPanel Margin="25,40,25,15">
                     <Grid x:Name="PanDebugAnim" Height="22" Margin="0,0,0,10">
                         <Grid.ColumnDefinitions>
@@ -198,7 +204,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>

+ 5 - 3
Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb

@@ -35,7 +35,7 @@
 
         'Mod 与整合包
         ComboDownloadTranslateV2.SelectedIndex = Setup.Get("ToolDownloadTranslateV2")
-        'ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod")
+        ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod")
         ComboModLocalNameStyle.SelectedIndex = Setup.Get("ToolModLocalNameStyle")
         CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
 
@@ -50,6 +50,7 @@
         ComboSystemUpdate.SelectedIndex = Setup.Get("SystemSystemUpdate")
         ComboSystemActivity.SelectedIndex = Setup.Get("SystemSystemActivity")
         TextSystemCache.Text = Setup.Get("SystemSystemCache")
+        CheckSystemTelemetry.Checked = Setup.Get("SystemSystemTelemetry")
 
         '调试选项
         CheckDebugMode.Checked = Setup.Get("SystemDebugMode")
@@ -81,6 +82,7 @@
             Setup.Reset("SystemSystemCache")
             Setup.Reset("SystemSystemUpdate")
             Setup.Reset("SystemSystemActivity")
+            Setup.Reset("SystemSystemTelemetry")
 
             Log("[Setup] 已初始化启动器页设置")
             Hint("已初始化启动器页设置!", HintType.Finish, False)
@@ -92,13 +94,13 @@
     End Sub
 
     '将控件改变路由到设置改变
-    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change
+    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change, CheckSystemTelemetry.Change
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked)
     End Sub
     Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderDebugAnim.Change, SliderDownloadThread.Change, SliderDownloadSpeed.Change
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value)
     End Sub
-    Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboModLocalNameStyle.SelectionChanged, ComboDownloadTranslateV2.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadSource.SelectionChanged
+    Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboModLocalNameStyle.SelectionChanged, ComboDownloadTranslateV2.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadSource.SelectionChanged, ComboDownloadMod.SelectionChanged
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex)
     End Sub
     Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextSystemCache.ValidatedTextChanged

+ 5 - 3
Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb

@@ -274,7 +274,7 @@
 
     '顶部栏
     Private Sub BtnLogoChange_Click(sender As Object, e As EventArgs) Handles BtnLogoChange.Click
-        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片")
+        Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.jpeg;*.gif;*.webp)|*.png;*.jpg;*.jpeg;*.gif;*.webp", "选择图片")
         If FileName = "" Then Return
         Try
             '拷贝文件
@@ -319,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
@@ -357,7 +357,9 @@ Refresh:
             PanMusicVolume.Visibility = Visibility.Visible
             PanMusicDetail.Visibility = Visibility.Visible
             BtnMusicClear.Visibility = Visibility.Visible
-            CardMusic.Title = "背景音乐(" & EnumerateFiles(Path & "PCL\Musics\").Count & " 首)"
+            CardMusic.Title = "背景音乐(" &
+                EnumerateFiles(Path & "PCL\Musics\").Count(Function(f) Not {".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png"}.Contains(f.Extension.ToLower)) &
+                " 首)"
         Else
             PanMusicVolume.Visibility = Visibility.Collapsed
             PanMusicDetail.Visibility = Visibility.Collapsed

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

@@ -102,7 +102,7 @@
                                 <local:MyCheckBox.Tag>
                                     <local:ExportOption 
                                 Title="Mod 设置"
-                                Rules="config/|!config/jei/world/|!config/worldedit/|config/worldedit/worldedit.properties|!config/spark/|config/spark/config.json|defaultconfigs/|journeymap/config/|journeymap/server/|TrashSlotSaveState.json|customfov.txt|gg.essential.mod/|essential/|!essential/*/|!essential/*.jar*|!essential/screenshot-checksum-caches.json|!essential/microsoft_accounts.json|paragliderSettings.nbt|local/client_config.json|local/ftbl.json|local/client/sidebar_buttons.json|local/client/ftbutilities.cfg|local/client/ftblib.cfg|local/client/xencraft.cfg|liteloader.properties|default_reference.xml|CustomSkinLoader/CustomSkinLoader.json"
+                                Rules="config/|!config/jei/world/|!config/worldedit/|config/worldedit/worldedit.properties|!config/spark/|config/spark/config.json|defaultconfigs/|journeymap/config/|journeymap/server/|TrashSlotSaveState.json|customfov.txt|gg.essential.mod/|essential/|!essential/*/|!essential/*.jar*|!essential/screenshot-checksum-caches.json|!essential/microsoft_accounts.json|paragliderSettings.nbt|local/client_config.json|local/ftbl.json|local/client/sidebar_buttons.json|local/client/ftbutilities.cfg|local/client/ftblib.cfg|local/client/xencraft.cfg|liteloader.properties|default_reference.xml|CustomSkinLoader/CustomSkinLoader.json|!config/tacz/custom"
                                 DefaultChecked="True" />
                                 </local:MyCheckBox.Tag>
                             </local:MyCheckBox>
@@ -133,6 +133,14 @@
                                 DefaultChecked="False" />
                                 </local:MyCheckBox.Tag>
                             </local:MyCheckBox>
+                            <local:MyCheckBox>
+                                <local:MyCheckBox.Tag>
+                                    <local:ExportOption 
+                                Title="TaCZ 枪包"
+                                Rules="tacz/|config/tacz/custom/"
+                                DefaultChecked="False" />
+                                </local:MyCheckBox.Tag>
+                            </local:MyCheckBox>
                             <local:MyCheckBox>
                                 <local:MyCheckBox.Tag>
                                     <local:ExportOption 
@@ -235,7 +243,7 @@
                         </StackPanel>
                     </StackPanel>
                 </local:MyCard>
-                <local:MyCard Title="高级选项" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+                <local:MyCard Title="高级选项" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
                     <StackPanel Margin="25,37,23,20">
                         <local:MyHint Text="建议仅在无法稳定连接 CurseForge 或 Modrinth 时才打包资源文件。&#xa;请不要公开发布含资源文件的整合包,这会违反部分 Mod 的使用协议!"
                                       Theme="Yellow" Margin="0,2,0,8" Visibility="{Binding Checked, ElementName=CheckAdvancedInclude, Converter={StaticResource BooleanToVisibilityConverter}}" />

+ 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 - 4
Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb

@@ -381,7 +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.TargetVersion = PageVersionLeft.Version
+        PageComp.TargetName = SearchBox.Text
         FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadMod)
     End Sub
 
@@ -428,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
 
@@ -636,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
@@ -732,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 被主动取消")

+ 28 - 29
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,27 +132,26 @@
             '清理 ini 缓存
             IniClearCache(PageVersionLeft.Version.PathIndie & "options.txt")
             IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini")
-            '遍历重命名所有文件与文件夹
-            For Each Entry As DirectoryInfo In New DirectoryInfo(NewPath).EnumerateDirectories
-                If Not Entry.Name.Contains(OldName) Then Continue For
+            '重命名 jar 文件与 natives 文件夹
+            '不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443)
+            If Directory.Exists($"{NewPath}{OldName}-natives") Then
                 If IsCaseChangedOnly Then
-                    My.Computer.FileSystem.RenameDirectory(Entry.FullName, Entry.Name & "_temp")
-                    My.Computer.FileSystem.RenameDirectory(Entry.FullName & "_temp", Entry.Name.Replace(OldName, NewName))
+                    My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{OldName}natives_temp")
+                    My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives_temp", $"{NewName}-natives")
                 Else
-                    DeleteDirectory(NewPath & Entry.Name.Replace(OldName, NewName))
-                    My.Computer.FileSystem.RenameDirectory(Entry.FullName, Entry.Name.Replace(OldName, NewName))
+                    DeleteDirectory($"{NewPath}{NewName}-natives")
+                    My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{NewName}-natives")
                 End If
-            Next
-            For Each Entry As FileInfo In New DirectoryInfo(NewPath).EnumerateFiles
-                If Not Entry.Name.Contains(OldName) Then Continue For
+            End If
+            If File.Exists($"{NewPath}{OldName}.jar") Then
                 If IsCaseChangedOnly Then
-                    My.Computer.FileSystem.RenameFile(Entry.FullName, Entry.Name & "_temp")
-                    My.Computer.FileSystem.RenameFile(Entry.FullName & "_temp", Entry.Name.Replace(OldName, NewName))
+                    My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{OldName}_temp.jar")
+                    My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}_temp.jar", $"{NewName}.jar")
                 Else
-                    If File.Exists(NewPath & Entry.Name.Replace(OldName, NewName)) Then File.Delete(NewPath & Entry.Name.Replace(OldName, NewName))
-                    My.Computer.FileSystem.RenameFile(Entry.FullName, Entry.Name.Replace(OldName, NewName))
+                    File.Delete($"{NewPath}{NewName}.jar")
+                    My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{NewName}.jar")
                 End If
-            Next
+            End If
             '替换版本设置文件中的路径
             If File.Exists(NewPath & "PCL\Setup.ini") Then
                 WriteFile(NewPath & "PCL\Setup.ini", ReadFile(NewPath & "PCL\Setup.ini").Replace(OldPath, NewPath))
@@ -159,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()
@@ -185,7 +184,7 @@
         '选择 自定义 时修改图片
         Try
             If ComboDisplayLogo.SelectedItem Is ItemDisplayLogoCustom Then
-                Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif", "选择图片")
+                Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpeg;*.jpg;*.gif;*.webp)|*.png;*.jpeg;*.jpg;*.gif;*.webp", "选择图片")
                 If FileName = "" Then
                     Reload() '还原选项
                     Return
@@ -331,7 +330,7 @@
                         DeleteDirectory(PageVersionLeft.Version.Path)
                         Hint("版本 " & PageVersionLeft.Version.Name & " 已永久删除!", HintType.Finish)
                     Else
-                        FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+                        FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
                         Hint("版本 " & PageVersionLeft.Version.Name & " 已删除到回收站!", HintType.Finish)
                     End If
                 Case 2

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

@@ -192,7 +192,7 @@
                     <local:MyButton x:Name="BtnServerAuthLittle" Height="35" MinWidth="150" Margin="0,12,0,0" Text="设置为 LittleSkin" HorizontalAlignment="Left" ColorType="Highlight" />
                 </StackPanel>
             </local:MyCard>
-            <local:MyCard x:Name="CardAdvance" Margin="0,0,0,15" Title="高级启动选项" IsSwaped="True" CanSwap="True">
+            <local:MyCard x:Name="CardAdvance" Margin="0,0,0,15" Title="高级启动选项" IsSwapped="True" CanSwap="True">
                 <Grid Margin="25,40,25,15">
                     <Grid.ColumnDefinitions>
                         <ColumnDefinition Width="Auto" SharedSizeGroup="Name" />
@@ -208,17 +208,19 @@
                         <RowDefinition Height="Auto" />
                         <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="跟随全局设置"
+                    <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Java 虚拟机参数" 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;支持 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="跟随全局设置" />
+                    <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;支持 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 文件夹。" />
                     <local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="VersionAdvanceRunWait" Visibility="Collapsed"
                                       Text="等待命令执行完成后再继续启动" Margin="0,5,0,0" />
                     <StackPanel Margin="0,12,0,4" Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Left">
+                        <local:MyCheckBox Height="28" Text="禁止 Mod 更新" x:Name="CheckAdvanceDisableModUpdate" Tag="VersionAdvanceDisableModUpdate"
+                                          ToolTipService.Placement="Right" />
                         <local:MyCheckBox Height="28" Text="忽略 Java 兼容性警告" x:Name="CheckAdvanceJava" Tag="VersionAdvanceJava"
                                           ToolTipService.Placement="Right"
                                           ToolTip="如果手动选择了与当前版本不兼容的 Java,则自动跳过兼容性警告弹窗,强制使用手动选择的 Java。" />

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

@@ -61,6 +61,7 @@
             CheckAdvanceAssetsV2.Checked = Setup.Get("VersionAdvanceAssetsV2", Version:=PageVersionLeft.Version)
             CheckAdvanceJava.Checked = Setup.Get("VersionAdvanceJava", Version:=PageVersionLeft.Version)
             CheckAdvanceDisableJLW.Checked = Setup.Get("VersionAdvanceDisableJLW", Version:=PageVersionLeft.Version)
+            CheckAdvanceDisableModUpdate.Checked = Setup.Get("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version)
 
         Catch ex As Exception
             Log(ex, "重载版本独立设置时出错", LogLevel.Feedback)
@@ -91,6 +92,7 @@
             Setup.Reset("VersionAdvanceRun", Version:=PageVersionLeft.Version)
             Setup.Reset("VersionAdvanceRunWait", Version:=PageVersionLeft.Version)
             Setup.Reset("VersionAdvanceDisableJLW", Version:=PageVersionLeft.Version)
+            Setup.Reset("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version)
 
             Setup.Reset("VersionArgumentJavaSelect", Version:=PageVersionLeft.Version)
             JavaSearchLoader.Start(IsForceRestart:=True)
@@ -125,7 +127,7 @@
     Private Shared Sub CheckBoxLikeComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndieV2.SelectionChanged
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex = 0, Version:=PageVersionLeft.Version)
     End Sub
-    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckAdvanceAssetsV2.Change, CheckAdvanceJava.Change, CheckAdvanceDisableJLW.Change
+    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckAdvanceAssetsV2.Change, CheckAdvanceJava.Change, CheckAdvanceDisableJLW.Change, CheckAdvanceDisableModUpdate.Change
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked, Version:=PageVersionLeft.Version)
     End Sub
 
@@ -355,6 +357,15 @@ PreFin:
 
 #Region "服务器"
 
+    '自动替换标点
+    Private Sub TextServerEnter_Change() Handles TextServerEnter.TextChanged
+        Dim NewText = TextServerEnter.Text.Replace(":", ":").Replace("。", ".")
+        If NewText = TextServerEnter.Text Then Return
+        Dim CurrentPosition = TextServerEnter.SelectionStart '重设焦点
+        TextServerEnter.Text = NewText
+        TextServerEnter.SelectionStart = CurrentPosition
+    End Sub
+
     '全局
     Private ComboServerLoginLast As Integer
     Private Sub ComboServerLogin_Changed() Handles ComboServerLogin.SelectionChanged, TextServerNide.ValidatedTextChanged, TextServerAuthServer.ValidatedTextChanged, TextServerAuthRegister.ValidatedTextChanged

+ 41 - 5
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" />
@@ -182,6 +205,7 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </ApplicationDefinition>
+    <Compile Include="Controls\Behaviors\ClipboardInterceptor.vb" />
     <Compile Include="Controls\IMyRadio.vb" />
     <Compile Include="Controls\MyComboBox.vb" />
     <Compile Include="Controls\MyComboBoxItem.vb" />
@@ -202,6 +226,7 @@
     <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>
@@ -235,9 +260,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>
@@ -724,6 +746,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" />
@@ -961,5 +984,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


+ 28 - 20
Plain Craft Launcher 2/Resources/Custom.xaml

@@ -3,7 +3,7 @@
      下列内容均基于 WPF 框架。你可以在继续之前先尝试自学 XAML 与 WPF 的基础,这能让你更轻松地理解下列内容。
      你也可以使用 Ctrl + F 快速查找。例如,若需要改变文本颜色,则搜索 “颜色” 即可。 -->
 
-<local:MyCard Title="纯文本" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="纯文本" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="每个 local:MyCard 代表一张卡片,你可以添加、删除格式类似的 MyCard 来添加多个卡片。每个 TextBlock 代表一段文本,你可以在 Text 属性中书写任何你想写的内容,也可以自行添加更多的 TextBlock。" />
@@ -14,17 +14,17 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="卡片、提示条" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="卡片、提示条" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <local:MyHint Text="local:MyHint 代表提示条。" />
         <local:MyHint Margin="0,8,0,2" Theme="Blue"
                     Text="将提示条的 Theme 属性改为 Blue、Yellow 或 Red 即可修改配色。&#xA;使用左边的那堆字符可以在任意地方手动换行,如果需要使用等号、引号等特殊字符,可以自行百度 XAML 转义字符。" />
         <TextBlock TextWrapping="Wrap" Margin="0,6,0,0"
-                    Text="卡片(local:MyCard)的 Title 属性决定了它的标题。&#xA;为卡片添加 CanSwap 属性让它可以被折叠,True 代表是,False 代表否。在此基础上,再使用 IsSwaped 属性调整它是否默认被折叠。" />
+                    Text="卡片(local:MyCard)的 Title 属性决定了它的标题。&#xA;为卡片添加 CanSwap 属性让它可以被折叠,True 代表是,False 代表否。在此基础上,再使用 IsSwapped 属性调整它是否默认被折叠。" />
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="长宽属性、图片" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="长宽属性、图片" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4" Width="320" HorizontalAlignment="Right"
                     Text="任意项目都可以添加 Width 与 Height 属性来控制宽高。HorizontalAlignment 属性可以控制对齐:Center 代表居中,Right 代表居右,例如这段文本就被居右了。" />
@@ -34,7 +34,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="按钮" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="按钮" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="添加 local:MyButton 即可新建一个按钮。你需要限定它的尺寸与位置,并通过 Padding 属性进一步控制它的内边距。" />
@@ -53,7 +53,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="事件 1:打开网页、弹出窗口" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="事件 1:打开网页、弹出窗口" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="将按钮的 EventType 属性设为 打开网页,然后在 EventData 属性中写入网址,即可通过点击按钮打开网页。" />
@@ -70,7 +70,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="事件 2:启动游戏" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="事件 2:启动游戏" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="将 EventType 设置为 启动游戏,EventData 设置为具体的游戏版本,即可通过点击按钮启动游戏。如果当前游戏文件夹没有该版本,则无法启动。" />
@@ -87,7 +87,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="事件 3:执行命令" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="事件 3:执行命令" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="将 EventType 设置为 执行命令(或 打开文件),EventData 设置为文件路径,即可点击打开特定文件或启动程序。" />
@@ -106,14 +106,14 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="事件 4:其他事件类型" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="事件 4:其他事件类型" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock Margin="0,4,0,6" FontWeight="Bold"
                     Text="EventType 共有以下种类:" />
         <TextBlock Margin="0,0,0,4"
                     Text="· 打开网页、启动游戏、打开文件:已在上方的卡片中介绍" />
         <local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="查看 PCL 源代码" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2/blob/main/Plain%20Craft%20Launcher%202/FormMain.xaml.vb"/>
+                    Text="查看 PCL 源代码" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL/blob/main/Plain%20Craft%20Launcher%202/FormMain.xaml.vb"/>
         <TextBlock Margin="0,0,0,4"
                     Text="· 今日人品、清理垃圾、内存优化、安装整合包:触发对应功能" />
         <TextBlock Margin="0,0,0,4"
@@ -134,10 +134,18 @@
                     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"/>
+		<TextBlock Margin="0,4,0,4"
+                    Text="· 修改设置:将 PCL 的特定设置修改为特定值"/>
+		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
+                    Text="将窗口改为半透明" EventType="修改设置" EventData="UiLauncherTransparent|400"/>
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="StackPanel 横向布局" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="StackPanel 横向布局" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="你需要使用 StackPanel 在一行里塞下多个按钮。你可以仅在这个教学卡片的基础上稍作调整,来实现自己的按钮布局。" />
@@ -162,7 +170,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="列表项 1:基础" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="列表项 1:基础" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="你也可以使用列表项 “local:MyListItem” 来替代按钮,其使用方式与按钮类似。" />
@@ -179,7 +187,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="列表项 2:内置图片" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="列表项 2:内置图片" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="PCL 内置了一些 Minecraft 方块与物品图片,可以直接使用。"/>
@@ -217,7 +225,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="主题色" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="主题色" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,15"
                     Text="你可以用类似 {DynamicResource ColorBrush5} 的格式使用 PCL 当前的主题颜色,修改末尾的数字编号以改变颜色浓度。" />
@@ -238,7 +246,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="替换标记" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="替换标记" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="在花括号 {} 中写入特定内容,PCL 会在加载时对其进行替换。&#xa;例如,path 会被替换为 PCL 可执行文件所在文件夹({path}),用于在 EventData 中指定特定文件,或是加载图片。" />
@@ -249,7 +257,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="进阶:Grid 布局" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="进阶:Grid 布局" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="如果要实现更复杂的布局,则必须使用 Grid。Grid 可以让按钮们自动适应窗口宽度:随意拉伸 PCL 窗口,按钮大小会自动改变。" />
@@ -272,7 +280,7 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="进阶:图标按钮" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="进阶:图标按钮" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,4"
                     Text="MyIconTextButton 是一种带有自定义矢量图标的按钮变体,它同样支持 EventType 与 EventData 属性。&#xa;通过设置 Logo 属性来控制它的图标,设置 LogoScale 属性还可以调整图标的缩放比例。" />
@@ -297,12 +305,12 @@
     </StackPanel>
 </local:MyCard>
 
-<local:MyCard Title="进阶:联网主页" Margin="0,0,0,15" CanSwap="True" IsSwaped="True">
+<local:MyCard Title="进阶:联网主页" Margin="0,0,0,15" CanSwap="True" IsSwapped="True">
     <StackPanel Margin="25,40,23,15">
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="如果你在尝试制作联网更新的主页,可以查看下方的 GitHub 讨论页。&#xa;你可以根据其中的介绍为主页添加版本号检查以节省流量,也可以通过检查 Referer 和 User Agent 来确定对方的 PCL 版本。" />
         <local:MyButton Margin="0,0,0,8" Height="35" HorizontalAlignment="Center" Padding="20,0,20,0"
-                    Text="打开 GitHub 讨论页" EventType="打开网页" EventData="https://github.com/Hex-Dragon/PCL2/discussions/2528" />
+                    Text="打开 GitHub 讨论页" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL/discussions/2528" />
     </StackPanel>
 </local:MyCard>
 
@@ -332,7 +340,7 @@ local:MyImage(图片):
 local:MyCard(卡片):
  - Title:设置显示的标题文本
  - CanSwap:卡片是否可以折叠,True 为是,False 为否
- - IsSwaped:卡片是否默认折叠,要求 CanSwap 必须为 True
+ - IsSwapped:卡片是否默认折叠,要求 CanSwap 必须为 True
  - HorizontalAlignment:若使用,要求 CanSwap 必须为 False
  - UseAnimation:是否在展开等高度改变时触发动画;True 为是(默认),False 为否
  - SwapLogoRight:卡片折叠时的箭头是朝下还是朝右;True 为朝右,False 为朝下(默认)

BIN
Plain Craft Launcher 2/Resources/Help.zip


Разница между файлами не показана из-за своего большого размера
+ 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


+ 3 - 3
README.md

@@ -1,7 +1,7 @@
 # Plain Craft Launcher
 
-[![Stars](https://img.shields.io/github/stars/Hex-Dragon/PCL2?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTggLjI1YS43NS43NSAwIDAgMSAuNjczLjQxOGwxLjg4MiAzLjgxNSA0LjIxLjYxMmEuNzUuNzUgMCAwIDEgLjQxNiAxLjI3OWwtMy4wNDYgMi45Ny43MTkgNC4xOTJhLjc1MS43NTEgMCAwIDEtMS4wODguNzkxTDggMTIuMzQ3bC0zLjc2NiAxLjk4YS43NS43NSAwIDAgMS0xLjA4OC0uNzlsLjcyLTQuMTk0TC44MTggNi4zNzRhLjc1Ljc1IDAgMCAxIC40MTYtMS4yOGw0LjIxLS42MTFMNy4zMjcuNjY4QS43NS43NSAwIDAgMSA4IC4yNVoiIGZpbGw9IiNlYWM1NGYiLz48L3N2Zz4=&logoSize=auto&label=Stars&labelColor=444444&color=eac54f)](https://github.com/Hex-Dragon/PCL2/)
-[![Issues](https://img.shields.io/github/issues/Hex-Dragon/PCL2?style=flat&label=Issues&labelColor=444444&color=1F883D)](https://github.com/Hex-Dragon/PCL2/issues)
+[![Stars](https://img.shields.io/github/stars/Meloong-Git/PCL?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTggLjI1YS43NS43NSAwIDAgMSAuNjczLjQxOGwxLjg4MiAzLjgxNSA0LjIxLjYxMmEuNzUuNzUgMCAwIDEgLjQxNiAxLjI3OWwtMy4wNDYgMi45Ny43MTkgNC4xOTJhLjc1MS43NTEgMCAwIDEtMS4wODguNzkxTDggMTIuMzQ3bC0zLjc2NiAxLjk4YS43NS43NSAwIDAgMS0xLjA4OC0uNzlsLjcyLTQuMTk0TC44MTggNi4zNzRhLjc1Ljc1IDAgMCAxIC40MTYtMS4yOGw0LjIxLS42MTFMNy4zMjcuNjY4QS43NS43NSAwIDAgMSA4IC4yNVoiIGZpbGw9IiNlYWM1NGYiLz48L3N2Zz4=&logoSize=auto&label=Stars&labelColor=444444&color=eac54f)](https://github.com/Meloong-Git/PCL/)
+[![Issues](https://img.shields.io/github/issues/Meloong-Git/PCL?style=flat&label=Issues&labelColor=444444&color=1F883D)](https://github.com/Meloong-Git/PCL/issues)
 [![哔哩哔哩](https://img.shields.io/badge/动态-BiliBili-00A4DB?style=flat&labelColor=444444&logoSize=auto)](https://space.bilibili.com/11343203/dynamic)
 [![爱发电](https://img.shields.io/badge/赞助-爱发电-946ce6?style=flat&labelColor=444444&logoSize=auto)](https://afdian.com/@LTCat)
 
@@ -13,5 +13,5 @@
 
 ### 相关资源
 - [PCL 下载](https://afdian.com/p/0164034c016c11ebafcb52540025c377):下载可免费使用的正式版 PCL!
-- [PCL 功能投票](https://github.com/Hex-Dragon/PCL2/discussions/2):来参加投票吧,开发者会优先处理票数较高的项目!
+- [PCL 功能投票](https://github.com/Meloong-Git/PCL/discussions/2):来参加投票吧,开发者会优先处理票数较高的项目!
 - [帮助文档库](https://github.com/LTCatt/PCL2Help):PCL 内置帮助文档的存储库

BIN
最新正式版.zip


Некоторые файлы не были показаны из-за большого количества измененных файлов