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

Merge branch 'main' into feat5684

龙腾猫跃 1 год назад
Родитель
Сommit
ab697054c8
100 измененных файлов с 4088 добавлено и 3779 удалено
  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. 35 19
      LICENCE
  9. 4 0
      Plain Craft Launcher 2/App.config
  10. 6 8
      Plain Craft Launcher 2/Application.xaml
  11. 55 70
      Plain Craft Launcher 2/Application.xaml.vb
  12. 187 0
      Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb
  13. 15 0
      Plain Craft Launcher 2/Controls/Interfaces.vb
  14. 3 3
      Plain Craft Launcher 2/Controls/MyButton.xaml.vb
  15. 90 68
      Plain Craft Launcher 2/Controls/MyCard.vb
  16. 6 6
      Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb
  17. 2 2
      Plain Craft Launcher 2/Controls/MyComboBox.vb
  18. 1 1
      Plain Craft Launcher 2/Controls/MyComboBoxItem.vb
  19. 66 41
      Plain Craft Launcher 2/Controls/MyDropShadow.vb
  20. 5 5
      Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
  21. 2 2
      Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb
  22. 4 12
      Plain Craft Launcher 2/Controls/MyHint.xaml
  23. 73 38
      Plain Craft Launcher 2/Controls/MyHint.xaml.vb
  24. 1 1
      Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb
  25. 1 1
      Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb
  26. 18 57
      Plain Craft Launcher 2/Controls/MyImage.vb
  27. 13 13
      Plain Craft Launcher 2/Controls/MyListItem.xaml.vb
  28. 6 10
      Plain Craft Launcher 2/Controls/MyLoading.xaml.vb
  29. 1 1
      Plain Craft Launcher 2/Controls/MyMenuItem.vb
  30. 3 2
      Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb
  31. 2 2
      Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb
  32. 3 3
      Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.vb
  33. 16 19
      Plain Craft Launcher 2/Controls/MyPageLeft.vb
  34. 14 14
      Plain Craft Launcher 2/Controls/MyPageRight.vb
  35. 5 5
      Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb
  36. 6 6
      Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb
  37. 2 2
      Plain Craft Launcher 2/Controls/MyResizer.vb
  38. 3 4
      Plain Craft Launcher 2/Controls/MyScrollViewer.vb
  39. 7 7
      Plain Craft Launcher 2/Controls/MySlider.xaml.vb
  40. 6 6
      Plain Craft Launcher 2/Controls/MyTextBox.vb
  41. 2 2
      Plain Craft Launcher 2/Controls/MyTextButton.vb
  42. 8 10
      Plain Craft Launcher 2/FormMain.xaml
  43. 222 105
      Plain Craft Launcher 2/FormMain.xaml.vb
  44. 21 1
      Plain Craft Launcher 2/Modules/Base/ModAnimation.vb
  45. 180 190
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  46. 52 55
      Plain Craft Launcher 2/Modules/Base/ModLoader.vb
  47. 562 656
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  48. 34 28
      Plain Craft Launcher 2/Modules/Base/ModValidate.vb
  49. 283 142
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  50. 121 94
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  51. 128 117
      Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
  52. 34 41
      Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
  53. 214 180
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  54. 209 200
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  55. 46 42
      Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
  56. 127 160
      Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
  57. 20 19
      Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb
  58. 1 1
      Plain Craft Launcher 2/Modules/ModEvent.vb
  59. 22 24
      Plain Craft Launcher 2/Modules/ModMain.vb
  60. 3 3
      Plain Craft Launcher 2/Modules/ModMusic.vb
  61. 69 74
      Plain Craft Launcher 2/Modules/ModSecret.vb
  62. 0 59
      Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb
  63. 151 0
      Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb
  64. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  65. 53 13
      Plain Craft Launcher 2/My Project/Resources.Designer.vb
  66. 19 7
      Plain Craft Launcher 2/My Project/Resources.resx
  67. 0 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml
  68. 35 24
      Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb
  69. 95 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml
  70. 115 37
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb
  71. 1 1
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml
  72. 113 76
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb
  73. 26 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml
  74. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml.vb
  75. 35 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml
  76. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml.vb
  77. 28 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml
  78. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml.vb
  79. 45 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml
  80. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml.vb
  81. 34 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml
  82. 2 0
      Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml.vb
  83. 109 96
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  84. 0 31
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml
  85. 0 111
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb
  86. 0 28
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml
  87. 0 32
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb
  88. 0 26
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml
  89. 0 61
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb
  90. 12 12
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
  91. 121 116
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
  92. 4 27
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml
  93. 49 96
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb
  94. 0 26
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml
  95. 0 52
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb
  96. 0 121
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml
  97. 0 27
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml
  98. 0 34
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb
  99. 0 26
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml
  100. 0 53
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb

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

@@ -12,7 +12,7 @@ body:
       required: false
       required: false
     - label: "**我已尝试使用 HMCL 启动,HMCL 没有出现问题。** 如果 HMCL 也无法启动就不是 PCL 导致的问题,请 **不要** 提交反馈。[下载 HMCL](https://hmcl.huangyuhui.net/download)"
     - label: "**我已尝试使用 HMCL 启动,HMCL 没有出现问题。** 如果 HMCL 也无法启动就不是 PCL 导致的问题,请 **不要** 提交反馈。[下载 HMCL](https://hmcl.huangyuhui.net/download)"
       required: true
       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
       required: true
 - type: textarea
 - type: textarea
   id: "yml-2"
   id: "yml-2"

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

@@ -12,7 +12,7 @@ body:
       required: false
       required: false
     - label: "我知晓大多数此类问题都是网络环境不佳导致的,但我确实认为我的问题可能是 PCL 导致的,和网络环境无关。"
     - label: "我知晓大多数此类问题都是网络环境不佳导致的,但我确实认为我的问题可能是 PCL 导致的,和网络环境无关。"
       required: true
       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
       required: true
 - type: textarea
 - type: textarea
   id: "yml-2"
   id: "yml-2"

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

@@ -8,7 +8,7 @@ body:
     label: "检查项"
     label: "检查项"
     description: "请逐个检查下列项目,并勾选确认。"
     description: "请逐个检查下列项目,并勾选确认。"
     options:
     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
       required: true
 - type: textarea
 - type: textarea
   id: "yml-2"
   id: "yml-2"

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

@@ -8,9 +8,9 @@ body:
     label: "检查项"
     label: "检查项"
     description: "请逐个检查下列项目,并勾选确认。"
     description: "请逐个检查下列项目,并勾选确认。"
     options:
     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
       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
       required: true
 - type: textarea
 - type: textarea
   id: "yml-2"
   id: "yml-2"

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

@@ -1,14 +1,14 @@
 blank_issues_enabled: false
 blank_issues_enabled: false
 contact_links:
 contact_links:
   - name: 主页预设反馈
   - name: 主页预设反馈
-    url: https://github.com/Hex-Dragon/PCL2/discussions/categories/自定义主页
+    url: https://github.com/Meloong-Git/PCL/discussions/categories/自定义主页
     about: 提交与预设的主页(设置 → 个性化 → 主页预设)中的具体内容相关的反馈
     about: 提交与预设的主页(设置 → 个性化 → 主页预设)中的具体内容相关的反馈
   - name: 帮助文档反馈
   - name: 帮助文档反馈
     url: https://github.com/LTCatt/PCL2Help/issues
     url: https://github.com/LTCatt/PCL2Help/issues
     about: 提交与 PCL 帮助文档(更多 → 帮助)中的具体内容相关的反馈
     about: 提交与 PCL 帮助文档(更多 → 帮助)中的具体内容相关的反馈
   - name: 提问
   - 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 相关的问题……
     about: 我想问一些 PCL 相关的问题……
   - name: 讨论
   - 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 相关的事情……
     about: 我想讨论一些 PCL 相关的事情……

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

@@ -8,9 +8,9 @@ body:
     label: "检查项"
     label: "检查项"
     description: "请逐个检查下列项目,并勾选确认。"
     description: "请逐个检查下列项目,并勾选确认。"
     options:
     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
       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
       required: true
     - label: "我知晓还没做的新功能真的太多了,忙不过来,所以新功能提案几乎不会被处理,也不建议再提交新功能提案 qwq……"
     - label: "我知晓还没做的新功能真的太多了,忙不过来,所以新功能提案几乎不会被处理,也不建议再提交新功能提案 qwq……"
       required: true
       required: true

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

@@ -38,7 +38,6 @@ jobs:
     
     
     - name: Update Help
     - name: Update Help
       run:  |
       run:  |
-        $workpath = [System.Environment]::CurrentDirectory
         Set-Location ..
         Set-Location ..
         git clone https://github.com/LTCatt/PCL2Help.git -b master --single-branch --depth 1
         git clone https://github.com/LTCatt/PCL2Help.git -b master --single-branch --depth 1
         Set-Location PCL2Help
         Set-Location PCL2Help
@@ -52,8 +51,8 @@ jobs:
 
 
     - name: Replace
     - name: Replace
       run: |
       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"
         (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
     - name: Build

+ 35 - 19
LICENCE

@@ -1,26 +1,40 @@
-本文件仅作为指引您合理使用本存储库内容的指南,不是法律文件或协议。
+一、声明
 
 
-一、承诺与声明。
+本文件仅作为指南,不是法律文件或协议。但开发者在此承诺,在您遵守本指南且善意、合理使用的情况下,不会追究您的责任。
+法律上,开发者仍保留所有权利(All Rights Reserved),若情况恶劣,仍可能将诉讼作为最终手段。
 
 
-  开发者承诺,在您遵守本指南且善意使用的情况下,不会追究您的责任,但最终裁量权仍由开发者保留。
-  法律上,开发者仍保留所有权利(All Rights Reserved),且可能随时修改本指南,本指南并不是永久性保证。
 
 
-二、少量使用情形。
 
 
-  如果您仅合理使用本存储库中的少量内容,或是参考实现思路,您必须:
-  1、给出适当的署名,且提供指向本存储库的链接。GitHub 上的 Fork 可视为满足此条。
-  2、不得以任何方式暗示开发者为您或您的使用作担保。
+二、轻度使用
 
 
-三、大量使用情形。
+若您仅参考了本存储库中极小部分的内容,或您在制作与 PCL 的实质功能不存在任何重叠的软件,则为轻度使用。
+其常见情形例如:
+· 使用或参考存储库中极小部分的内容,来开发启动器或其他软件。
+· 虽参考了较大量的内容,但制作的软件与 PCL 的实质功能不存在任何重叠。
 
 
-  如果您基于该存储库创作衍生作品,或合理使用了本存储库中的较大量内容,您必须:
-  1、给出适当的署名,且提供指向本存储库的链接。GitHub 上的 Fork 可视为满足此条。
-  2、不得以任何方式暗示开发者为您或您的使用作担保。
-  3、给出适当的说明,以明确表示该衍生作品是非官方内容。
-  4、使用本指南文件作为协议,不得附加额外条款。
-  5、该衍生作品的名称必须以 “PCL” 或 “Plain Craft Launcher” 开头,并在其后添加后缀。例如 “PCL 整合包优化版”、“PCL PR Collection”。
-  6、该衍生作品不得包含 PCL 的赞助解锁功能,或与其相近的功能或内容。例如将配色修改为蓝色色系以外的颜色。
-  7、以善意目的进行合理使用,不要用 PCL 来干坏事、恰烂钱、整烂活。开发者可能会将撤销所有授权作为最终手段。
+若属于轻度使用,您需要:
+1、以适当的形式给出署名。
+2、不得将您的内容与 PCL 混淆,或暗示您的内容与 PCL 有关。
+  例如:您的内容不得使用 Planting Craft Launcher、PCL 手机版 等会造成混淆或暗示这与 PCL 有关的名称。
+
+
+
+三、重度使用
+
+若不符合轻度使用的情形,即为重度使用。
+其常见情形例如:
+· 基于本存储库进行修改。
+· 参考存储库中较大量的内容,以制作或重新实现 PCL 的部分实质功能(例如启动或下载 Java 版 Minecraft,下载或管理 Mod 等)。
+
+若属于重度使用,您需要:
+1、明确表示您的内容是第三方基于 PCL 独立进行二次创作的产物。
+2、您的内容的名称必须以 Plain Craft Launcher (PCL) 开头,并在其后添加足以表现其由第三方修改的后缀。
+  允许的示例:PCL 某服务器魔改版、PCL PR Collection、PCL (XX Bug Fix)、PCL XXX修改版。
+  禁止的示例:PCL 某服务器特供版(暗示由官方提供)、PCL 3、PCL 手机版、PCL++、PCL Pro(这像是官方的后续作品)。
+3、在关于页面的首位给出龙腾猫跃的署名及对应的赞助链接,公开源代码,且继续使用本文件作为指南(可参考 CC BY-SC 协议)。
+4、软件若有 Minecraft 启动功能,则应实现与 McLaunchPrecheck 函数一致的 Minecraft 正版购买弹窗与赞助弹窗。
+5、软件在 Windows 系统上运行时,不应有与 PCL 的赞助解锁功能类似的功能或表现。
+  例如:界面的主要色相必须为蓝色(即使没有修改功能,改为其他色相也和需要赞助解锁的主题功能类似)。
 
 
 ————————————
 ————————————
 
 
@@ -29,5 +43,7 @@
 因为没有法律专业人士检查,所以这不敢作为正式的法律协议,如果有专业人士愿意帮忙改一改我也很乐意……
 因为没有法律专业人士检查,所以这不敢作为正式的法律协议,如果有专业人士愿意帮忙改一改我也很乐意……
 这些规则主要是为了阻止恶意的使用和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧!
 这些规则主要是为了阻止恶意的使用和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧!
 
 
-如果你只是想参考一下,或者用了一小部分代码,署个名就行啦。
-如果你整了个魔改版,或者用了一大堆代码,请仔细参考上面的第三条。
+如果你只是参考了一小段代码,署个名就行啦,不用担心。
+如果对具体细节有疑问,欢迎在 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"/>
                   <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
                   <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
                   <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
             </dependentAssembly>
             </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>
         </assemblyBinding>
     </runtime>
     </runtime>
 </configuration>
 </configuration>

+ 6 - 8
Plain Craft Launcher 2/Application.xaml

@@ -1,6 +1,7 @@
 <Application x:Class="Application"
 <Application x:Class="Application"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:behaviors="clr-namespace:PCL.Controls.Behaviors"
     xmlns:local="clr-namespace:PCL"
     xmlns:local="clr-namespace:PCL"
     xmlns:sys="clr-namespace:System;assembly=mscorlib"
     xmlns:sys="clr-namespace:System;assembly=mscorlib"
     StartupUri="FormMain.xaml"
     StartupUri="FormMain.xaml"
@@ -8,12 +9,6 @@
     <Application.Resources>
     <Application.Resources>
         <ResourceDictionary>
         <ResourceDictionary>
             
             
-            <!-- 本地化 -->
-            <ResourceDictionary.MergedDictionaries>
-                <ResourceDictionary Source="pack://application:,,,/Resources/Language/zh_CN.xaml" />
-                <ResourceDictionary Source="pack://application:,,,/Resources/Language/zh_CN.xaml" />
-            </ResourceDictionary.MergedDictionaries>
-            
             <!-- Converter -->
             <!-- Converter -->
             <local:AdditionConverter x:Key="AdditionConverter"/>
             <local:AdditionConverter x:Key="AdditionConverter"/>
             <local:MultiplicationConverter x:Key="MultiplicationConverter"/>
             <local:MultiplicationConverter x:Key="MultiplicationConverter"/>
@@ -66,7 +61,7 @@
             <SolidColorBrush x:Key="ColorBrushWhite" Color="#ffffff" />
             <SolidColorBrush x:Key="ColorBrushWhite" Color="#ffffff" />
             <SolidColorBrush x:Key="ColorBrushTransparent" Color="Transparent" />
             <SolidColorBrush x:Key="ColorBrushTransparent" Color="Transparent" />
             <SolidColorBrush x:Key="ColorBrushSemiTransparent">#01eaf2fe</SolidColorBrush>
             <SolidColorBrush x:Key="ColorBrushSemiTransparent">#01eaf2fe</SolidColorBrush>
-            <SolidColorBrush x:Key="ColorBrushBackgroundTransparentSidebar" Color="#D2FFFFFF" />
+            <SolidColorBrush x:Key="ColorBrushBackgroundTransparentSidebar" Color="#F1FFFFFF" />
 
 
             <!-- Window -->
             <!-- Window -->
             <Style TargetType="{x:Type Window}" x:Key="MyWindow">
             <Style TargetType="{x:Type Window}" x:Key="MyWindow">
@@ -209,7 +204,7 @@
                     </Setter.Value>
                     </Setter.Value>
                 </Setter>
                 </Setter>
             </Style>
             </Style>
-            <Style TargetType="local:MyScrollViewer">
+            <Style TargetType="local:MyScrollViewer" x:Name="StyleMyScrollViewer">
                 <Setter Property="OverridesDefaultStyle" Value="True" />
                 <Setter Property="OverridesDefaultStyle" Value="True" />
                 <Setter Property="CanContentScroll" Value="False" />
                 <Setter Property="CanContentScroll" Value="False" />
                 <Setter Property="PanningMode" Value="VerticalOnly" />
                 <Setter Property="PanningMode" Value="VerticalOnly" />
@@ -225,6 +220,7 @@
                     </Setter.Value>
                     </Setter.Value>
                 </Setter>
                 </Setter>
             </Style>
             </Style>
+            <Style TargetType="local:PageComp" BasedOn="{StaticResource {x:Type local:MyScrollViewer}}" />
             <Style TargetType="ScrollViewer" x:Key="ScrollViewerFullMargin">
             <Style TargetType="ScrollViewer" x:Key="ScrollViewerFullMargin">
                 <Setter Property="OverridesDefaultStyle" Value="True" />
                 <Setter Property="OverridesDefaultStyle" Value="True" />
                 <Setter Property="CanContentScroll" Value="False" />
                 <Setter Property="CanContentScroll" Value="False" />
@@ -307,6 +303,7 @@
                         </ControlTemplate>
                         </ControlTemplate>
                     </Setter.Value>
                     </Setter.Value>
                 </Setter>
                 </Setter>
+                <Setter Property="behaviors:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
             </Style>
             </Style>
             <Style TargetType="PasswordBox">
             <Style TargetType="PasswordBox">
                 <Setter Property="FontSize" Value="13" />
                 <Setter Property="FontSize" Value="13" />
@@ -348,6 +345,7 @@
                         </ControlTemplate>
                         </ControlTemplate>
                     </Setter.Value>
                     </Setter.Value>
                 </Setter>
                 </Setter>
+                <Setter Property="behaviors:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
             </Style>
             </Style>
 
 
             <!-- ComboBox -->
             <!-- ComboBox -->

+ 55 - 70
Plain Craft Launcher 2/Application.xaml.vb

@@ -19,6 +19,9 @@ Public Class Application
     '开始
     '开始
     Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
     Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
         Try
         Try
+            '动态 DLL 调用(必须尽量在前面,否则模块加载 CacheCow 等 DLL 就可能导致崩溃)
+            AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve
+            '开始
             SecretOnApplicationStart()
             SecretOnApplicationStart()
             '检查参数调用
             '检查参数调用
             If e.Args.Length > 0 Then
             If e.Args.Length > 0 Then
@@ -67,9 +70,9 @@ Public Class Application
                 If Not CheckPermission(PathTemp) Then Throw New Exception("PCL 没有对 " & PathTemp & " 的访问权限")
                 If Not CheckPermission(PathTemp) Then Throw New Exception("PCL 没有对 " & PathTemp & " 的访问权限")
             Catch ex As Exception
             Catch ex As Exception
                 If PathTemp = IO.Path.GetTempPath() & "PCL\" Then
                 If PathTemp = IO.Path.GetTempPath() & "PCL\" Then
-                    MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用")
+                    MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDetail(), "缓存文件夹不可用")
                 Else
                 Else
-                    MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用")
+                    MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDetail(), "缓存文件夹不可用")
                     Setup.Set("SystemSystemCache", "")
                     Setup.Set("SystemSystemCache", "")
                     PathTemp = IO.Path.GetTempPath() & "PCL\"
                     PathTemp = IO.Path.GetTempPath() & "PCL\"
                 End If
                 End If
@@ -104,18 +107,27 @@ WaitRetry:
             ToolTipService.PlacementProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(Primitives.PlacementMode.Bottom))
             ToolTipService.PlacementProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(Primitives.PlacementMode.Bottom))
             ToolTipService.HorizontalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(8.0))
             ToolTipService.HorizontalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(8.0))
             ToolTipService.VerticalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(4.0))
             ToolTipService.VerticalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(4.0))
+            '设置网络配置默认值
+            ServicePointManager.Expect100Continue = False
+            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
+            ServicePointManager.DefaultConnectionLimit = 10000
+            ServicePointManager.UseNagleAlgorithm = False
+            ServicePointManager.EnableDnsRoundRobin = True
+            ServicePointManager.ReusePort = True
             '设置初始窗口
             '设置初始窗口
             If Setup.Get("UiLauncherLogo") Then
             If Setup.Get("UiLauncherLogo") Then
                 FrmStart = New SplashScreen("Images\icon.ico")
                 FrmStart = New SplashScreen("Images\icon.ico")
                 FrmStart.Show(False, True)
                 FrmStart.Show(False, True)
             End If
             End If
-            '动态 DLL 调用
-            AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve
             '日志初始化
             '日志初始化
             LogStart()
             LogStart()
             '添加日志
             '添加日志
             Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
             Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
+#If RELEASE Then
+            Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",正式版", "")}")
+#Else
             Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
             Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
+#End If
             Log($"[Start] 程序路径:{PathWithName}")
             Log($"[Start] 程序路径:{PathWithName}")
             Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
             Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
             Log($"[Start] 管理员权限:{IsAdmin()}")
             Log($"[Start] 管理员权限:{IsAdmin()}")
@@ -131,10 +143,7 @@ WaitRetry:
             Setup.Load("SystemDebugAnim")
             Setup.Load("SystemDebugAnim")
             Setup.Load("ToolDownloadThread")
             Setup.Load("ToolDownloadThread")
             Setup.Load("ToolDownloadCert")
             Setup.Load("ToolDownloadCert")
-            '网络配置初始化
-            ServicePointManager.Expect100Continue = True
-            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
-            ServicePointManager.DefaultConnectionLimit = 1024
+            Setup.Load("ToolDownloadSpeed")
             '计时
             '计时
             Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
             Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
             ApplicationStartTick = GetTimeTick()
             ApplicationStartTick = GetTimeTick()
@@ -149,7 +158,7 @@ WaitRetry:
                 FilePath = PathWithName
                 FilePath = PathWithName
             Catch
             Catch
             End Try
             End Try
-            MsgBox(GetExceptionDetail(ex, True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
+            MsgBox(ex.GetDetail(True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
             FormMain.EndProgramForce(ProcessReturnValues.Exception)
             FormMain.EndProgramForce(ProcessReturnValues.Exception)
         End Try
         End Try
     End Sub
     End Sub
@@ -160,79 +169,55 @@ WaitRetry:
     End Sub
     End Sub
 
 
     '异常
     '异常
-    Private IsCritErrored As Boolean = False
     Private Sub Application_DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
     Private Sub Application_DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
         On Error Resume Next
         On Error Resume Next
-        e.Handled = True
-        If IsProgramEnded Then Exit Sub
-        If IsCritErrored Then
-            '在汇报错误后继续引发错误,知道这次压不住了
-            FormMain.EndProgramForce(ProcessReturnValues.Exception)
-            Exit Sub
+        '触发页面的 Dispatcher
+        If FrmMain?.PageLeft IsNot Nothing AndAlso TypeOf FrmMain.PageLeft Is IDispatcherUnhandledException Then
+            CType(FrmMain.PageLeft, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
+            If e.Handled Then Return
+        End If
+        If FrmMain?.PageRight IsNot Nothing AndAlso TypeOf FrmMain.PageRight Is IDispatcherUnhandledException Then
+            CType(FrmMain.PageRight, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
+            If e.Handled Then Return
         End If
         End If
-        IsCritErrored = True
-        Dim ExceptionString As String = GetExceptionDetail(e.Exception, True)
-        If ExceptionString.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse
-           ExceptionString.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse
-           ExceptionString.Contains(".NET Framework") OrElse ' “自动错误判断” 的结果分析
-           ExceptionString.Contains("未能加载文件或程序集") Then
+        '正常处理
+        e.Handled = True
+        If IsProgramEnded Then Return
+        FeedbackInfo()
+        Dim Detail As String = e.Exception.GetDetail(True)
+        If Detail.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse Detail.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse Detail.Contains("未能加载文件或程序集") OrElse
+           Detail.Contains(".NET Framework") Then ' “自动错误判断” 的结果分析
             OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-offline-installer")
             OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-offline-installer")
-            MsgBox("你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.6.2!", MsgBoxStyle.Information, "运行环境错误")
-            FormMain.EndProgramForce(ProcessReturnValues.Cancel)
+            Log(e.Exception, "你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.6.2!" & vbCrLf & "若无法安装,可在卸载高版本的 .NET Framework 后再试。", LogLevel.Critical, "运行环境错误")
         Else
         Else
-            FeedbackInfo()
-            Log(e.Exception, "程序出现未知错误", LogLevel.Assert, "锟斤拷烫烫烫")
+            Log(e.Exception, "程序出现未知错误", LogLevel.Critical, "锟斤拷烫烫烫")
         End If
         End If
     End Sub
     End Sub
 
 
     '动态 DLL 调用
     '动态 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
     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"))
                     WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64"))
                 End If
                 End If
-                Return AssemblyImazenWebp
-            End SyncLock
-        Else
-            Return Nothing
-        End If
+            End If
+            Return LoadedAssembly(Prefix)
+        End SyncLock
     End Function
     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

+ 15 - 0
Plain Craft Launcher 2/Controls/Interfaces.vb

@@ -0,0 +1,15 @@
+Imports System.Windows.Threading
+
+''' <summary>
+''' 指示页面可以通过按下 F5 或以其他方式刷新。
+''' </summary>
+Public Interface IRefreshable
+    Sub Refresh()
+End Interface
+
+''' <summary>
+''' 指示页面可以处理未捕获的异常。
+''' </summary>
+Public Interface IDispatcherUnhandledException
+    Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs)
+End Interface

+ 3 - 3
Plain Craft Launcher 2/Controls/MyButton.xaml.vb

@@ -142,7 +142,7 @@ Public Class MyButton
 
 
     '实现自定义事件
     '实现自定义事件
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
-        If Not IsMouseDown Then Exit Sub
+        If Not IsMouseDown Then Return
         Log("[Control] 按下按钮:" & Text)
         Log("[Control] 按下按钮:" & Text)
         RaiseEvent Click(sender, e)
         RaiseEvent Click(sender, e)
         If Not String.IsNullOrEmpty(Tag) Then
         If Not String.IsNullOrEmpty(Tag) Then
@@ -197,7 +197,7 @@ Public Class MyButton
         AniStart(AaColor(PanFore, BackgroundProperty, If(_ColorType = ColorState.Red, "ColorBrushRedBack", "ColorBrush7"), AnimationColorIn), "MyButton Background " & Uuid)
         AniStart(AaColor(PanFore, BackgroundProperty, If(_ColorType = ColorState.Red, "ColorBrushRedBack", "ColorBrush7"), AnimationColorIn), "MyButton Background " & Uuid)
     End Sub
     End Sub
     Private Sub Button_MouseUp() Handles Me.MouseLeftButtonUp
     Private Sub Button_MouseUp() Handles Me.MouseLeftButtonUp
-        If Not IsMouseDown Then Exit Sub
+        If Not IsMouseDown Then Return
         IsMouseDown = False
         IsMouseDown = False
         AniStart({
         AniStart({
                AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 300, 10, New AniEaseOutFluent(AniEasePower.Middle))
                AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 300, 10, New AniEaseOutFluent(AniEasePower.Middle))
@@ -205,7 +205,7 @@ Public Class MyButton
     End Sub
     End Sub
     Private Sub Button_MouseLeave() Handles Me.MouseLeave
     Private Sub Button_MouseLeave() Handles Me.MouseLeave
         AniStart(AaColor(PanFore, BackgroundProperty, "ColorBrushHalfWhite", AnimationColorOut), "MyButton Background " & Uuid)
         AniStart(AaColor(PanFore, BackgroundProperty, "ColorBrushHalfWhite", AnimationColorOut), "MyButton Background " & Uuid)
-        If Not IsMouseDown Then Exit Sub
+        If Not IsMouseDown Then Return
         IsMouseDown = False
         IsMouseDown = False
         AniStart(AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)), "MyButton Scale " & Uuid)
         AniStart(AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)), "MyButton Scale " & Uuid)
     End Sub
     End Sub

+ 90 - 68
Plain Craft Launcher 2/Controls/MyCard.vb

@@ -3,7 +3,7 @@
     '控件
     '控件
     Inherits Grid
     Inherits Grid
     Private ReadOnly MainGrid As Grid
     Private ReadOnly MainGrid As Grid
-    Public ReadOnly Property MainChrome As SystemDropShadowChrome
+    Public ReadOnly Property MainChrome As MyDropShadow
     Private ReadOnly MainBorder As Border
     Private ReadOnly MainBorder As Border
     Public Property BorderChild As UIElement
     Public Property BorderChild As UIElement
         Get
         Get
@@ -63,17 +63,18 @@
 
 
     'UI 建立
     'UI 建立
     Public Sub New()
     Public Sub New()
-        MainChrome = New SystemDropShadowChrome With {.Margin = New Thickness(-9.5, -9, 0.5, -0.5), .Opacity = 0.1, .CornerRadius = New CornerRadius(6)}
-        MainChrome.SetResourceReference(SystemDropShadowChrome.ColorProperty, "ColorObject1")
+        MainChrome = New MyDropShadow With {
+            .Margin = New Thickness(-3, -3, -3, -3 - GetWPFSize(1)), .ShadowRadius = 3, .Opacity = DropShadowIdleOpacity, .CornerRadius = New CornerRadius(5)}
+        MainChrome.SetResourceReference(MyDropShadow.ColorProperty, "ColorObject1")
         Children.Insert(0, MainChrome)
         Children.Insert(0, MainChrome)
-        MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(205, 255, 255, 255)), .CornerRadius = New CornerRadius(6), .IsHitTestVisible = False}
+        MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(245, 255, 255, 255)), .CornerRadius = New CornerRadius(5), .IsHitTestVisible = False}
         Children.Insert(1, MainBorder)
         Children.Insert(1, MainBorder)
         MainGrid = New Grid
         MainGrid = New Grid
         Children.Add(MainGrid)
         Children.Add(MainGrid)
     End Sub
     End Sub
     Private IsLoad As Boolean = False
     Private IsLoad As Boolean = False
     Private Sub Init() Handles Me.Loaded
     Private Sub Init() Handles Me.Loaded
-        If IsLoad Then Exit Sub
+        If IsLoad Then Return
         IsLoad = True
         IsLoad = True
         '初次加载限定
         '初次加载限定
         If MainTextBlock Is Nothing Then
         If MainTextBlock Is Nothing Then
@@ -89,7 +90,7 @@
             MainGrid.Children.Add(MainSwap)
             MainGrid.Children.Add(MainSwap)
         End If
         End If
         '改变默认的折叠
         '改变默认的折叠
-        If IsSwaped AndAlso SwapControl IsNot Nothing Then
+        If IsSwapped AndAlso SwapControl IsNot Nothing Then
             MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0))
             MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0))
             '取消由于高度变化被迫触发的高度动画
             '取消由于高度变化被迫触发的高度动画
             Dim RawUseAnimation As Boolean = UseAnimation
             Dim RawUseAnimation As Boolean = UseAnimation
@@ -108,7 +109,7 @@
         '这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎
         '这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎
         '但是现在……反正勉强能用……懒得改了就这样吧.jpg
         '但是现在……反正勉强能用……懒得改了就这样吧.jpg
         '别骂了别骂了.jpg
         '别骂了别骂了.jpg
-        If IsNothing(Stack.Tag) Then Exit Sub
+        If IsNothing(Stack.Tag) Then Return
         '排序
         '排序
         Select Case Type
         Select Case Type
             Case 3
             Case 3
@@ -122,14 +123,6 @@
         End Select
         End Select
         '控件转换
         '控件转换
         Select Case Type
         Select Case Type
-            Case 5
-                Dim LoadingPickaxe As New MyLoading With {.Text = "正在获取版本列表", .Margin = New Thickness(5)}
-                Dim Loader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Main", AddressOf DlForgeVersionMain)
-                LoadingPickaxe.State = Loader
-                Loader.Start(Stack.Tag)
-                AddHandler LoadingPickaxe.StateChanged, AddressOf FrmDownloadForge.Forge_StateChanged
-                AddHandler LoadingPickaxe.Click, AddressOf FrmDownloadForge.Forge_Click
-                Stack.Children.Add(LoadingPickaxe)
             Case 6
             Case 6
                 ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
                 ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
             Case 8
             Case 8
@@ -144,9 +137,6 @@
                     Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
                     Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
                 Case 3
                 Case 3
                     Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True))
                     Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True))
-                Case 4
-                    Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf FrmDownloadLiteLoader.DownloadStart, False))
-                Case 5
                 Case 6
                 Case 6
                     Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True))
                     Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True))
                 Case 7
                 Case 7
@@ -186,29 +176,29 @@
         Stack.Tag = Nothing
         Stack.Tag = Nothing
     End Sub
     End Sub
 
 
-    '事件
+    '动画
+    Private Const DropShadowIdleOpacity As Double = 0.07
+    Private Const DropShadowHoverOpacity As Double = 0.4
     Public Property HasMouseAnimation As Boolean = True
     Public Property HasMouseAnimation As Boolean = True
     Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter
     Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter
-        If Not HasMouseAnimation Then Exit Sub
+        If Not HasMouseAnimation Then Return
         Dim AniList As New List(Of AniData)
         Dim AniList As New List(Of AniData)
-        If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 150))
-        If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 150))
+        If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 90))
+        If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 90))
         AniList.AddRange({
         AniList.AddRange({
-            AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject2", 180),
-            AaColor(MainBorder, Border.BackgroundProperty, New MyColor(230, 255, 255, 255) - MainBorder.Background, 180),
-            AaOpacity(MainChrome, 0.3 - MainChrome.Opacity, 180)
+            AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject4", 90),
+            AaOpacity(MainChrome, DropShadowHoverOpacity - MainChrome.Opacity, 90)
         })
         })
         AniStart(AniList, "MyCard Mouse " & Uuid)
         AniStart(AniList, "MyCard Mouse " & Uuid)
     End Sub
     End Sub
     Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
     Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
-        If Not HasMouseAnimation Then Exit Sub
+        If Not HasMouseAnimation Then Return
         Dim AniList As New List(Of AniData)
         Dim AniList As New List(Of AniData)
-        If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 250))
-        If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 250))
+        If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 90))
+        If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 90))
         AniList.AddRange({
         AniList.AddRange({
-            AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject1", 300),
-            AaColor(MainBorder, Border.BackgroundProperty, New MyColor(205, 255, 255, 255) - MainBorder.Background, 300),
-            AaOpacity(MainChrome, 0.1 - MainChrome.Opacity, 300)
+            AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject1", 90),
+            AaOpacity(MainChrome, DropShadowIdleOpacity - MainChrome.Opacity, 90)
         })
         })
         AniStart(AniList, "MyCard Mouse " & Uuid)
         AniStart(AniList, "MyCard Mouse " & Uuid)
     End Sub
     End Sub
@@ -222,46 +212,64 @@
     Private IsHeightAnimating As Boolean = False
     Private IsHeightAnimating As Boolean = False
     Private ActualUsedHeight As Double '回滚实际高度(例如 NaN)
     Private ActualUsedHeight As Double '回滚实际高度(例如 NaN)
     Private Sub MySizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged
     Private Sub MySizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged
-        If Not UseAnimation Then Exit Sub
-        Dim DeltaHeight As Double = If(IsSwaped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height
+        If Not UseAnimation Then Return
+        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 Exit Sub
+        If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Return
         StartHeightAnimation(DeltaHeight, e.PreviousSize.Height, False)
         StartHeightAnimation(DeltaHeight, e.PreviousSize.Height, False)
     End Sub
     End Sub
-    Private Sub StartHeightAnimation(DeltaHeight As Double, PreviousHeight As Double, IsLoadAnimation As Boolean)
-        If IsHeightAnimating OrElse FrmMain Is Nothing Then Exit Sub '避免 XAML 设计器出错
+    Private Sub StartHeightAnimation(Delta As Double, PreviousHeight As Double, IsLoadAnimation As Boolean)
+        If IsHeightAnimating OrElse FrmMain Is Nothing Then Return '避免 XAML 设计器出错
 
 
         Dim AnimList As New List(Of AniData)
         Dim AnimList As New List(Of AniData)
-        If DeltaHeight > 10 OrElse (DeltaHeight < -10 AndAlso Not IsNothing(SwapControl)) Then '如果不是需要折叠的卡片,高度减小时的弹跳会吞掉按钮下边框
-            '高度增加较大,使用弹起动画
-            Dim Delta As Double = MathClamp(Math.Abs(DeltaHeight) * 0.05, 3, 10) * Math.Sign(DeltaHeight)
-            AnimList.AddRange({
-                AaHeight(Me, DeltaHeight + Delta, 300, If(IsLoadAnimation, 30, 0), If(DeltaHeight > FrmMain.Height, New AniEaseInFluent(AniEasePower.ExtraStrong), New AniEaseOutFluent(AniEasePower.ExtraStrong))),
-                AaHeight(Me, -Delta, 150, 260, Ease:=New AniEaseOutFluent(AniEasePower.Strong))
-            })
+        Dim AbsDelta = Math.Abs(Delta)
+
+        If AbsDelta <= 800 Then
+            '短距离,直接使用 150ms 的缓动动画
+            AnimList.Add(AaHeight(Me, Delta, 150,, New AniEaseOutFluent(AniEasePower.ExtraStrong)))
         Else
         Else
-            '普通的改变就行啦
-            AnimList.AddRange({
-                AaHeight(Me, DeltaHeight, MathClamp(Math.Abs(DeltaHeight) * 4, 150, 250),, New AniEaseOutFluent)
-            })
+            Dim EaseLength As Integer, EaseTime As Integer
+            Dim InitSpeed As Integer '到达缓动区前的初速度
+            If Delta < 0 AndAlso AbsDelta - EaseLength > 5000 * 0.1 Then
+                '收回距离过长 (>0.1s),强制以 100ms 完成匀速段,然后让减速段更长
+                EaseLength = 200
+                EaseTime = 150
+                InitSpeed = (AbsDelta - EaseLength) / 0.1
+            ElseIf Delta > 0 AndAlso AbsDelta - EaseLength > 5000 * 0.6 Then
+                '展开距离过长 (>0.6s),以 5000 速度展示 300ms 匀速段,剩下的距离全部归入减速段
+                InitSpeed = 5000
+                EaseLength = AbsDelta - InitSpeed * 0.3
+                EaseTime = 400
+            Else
+                '中程,匀速地快速展开(或收回)
+                EaseLength = 150
+                EaseTime = 200
+                InitSpeed = 4000
+            End If
+            '匀速段
+            AnimList.Add(AaHeight(Me, (AbsDelta - EaseLength) * Math.Sign(Delta),
+                (AbsDelta - EaseLength) / InitSpeed * 1000))
+            '减速段
+            AnimList.Add(AaHeight(Me, EaseLength * Math.Sign(Delta),
+                EaseTime,, New AniEaseOutFluentWithInitial(InitSpeed, EaseTime / 1000, EaseLength), True))
         End If
         End If
+
         AnimList.Add(AaCode(
         AnimList.Add(AaCode(
         Sub()
         Sub()
             IsHeightAnimating = False
             IsHeightAnimating = False
             Height = ActualUsedHeight
             Height = ActualUsedHeight
-            If IsSwaped Then SwapControl.Visibility = Visibility.Collapsed
+            If IsSwapped AndAlso SwapControl IsNot Nothing Then SwapControl.Visibility = Visibility.Collapsed
         End Sub,, True))
         End Sub,, True))
         AniStart(AnimList, "MyCard Height " & Uuid)
         AniStart(AnimList, "MyCard Height " & Uuid)
-
         IsHeightAnimating = True
         IsHeightAnimating = True
-        ActualUsedHeight = If(IsSwaped, SwapedHeight, Height)
+        ActualUsedHeight = If(IsSwapped, SwapedHeight, Height)
         Height = PreviousHeight
         Height = PreviousHeight
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。
     ''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。
     ''' </summary>
     ''' </summary>
     Public Sub TriggerForceResize()
     Public Sub TriggerForceResize()
-        Height = If(IsSwaped, SwapedHeight, Double.NaN)
+        Height = If(IsSwapped, SwapedHeight, Double.NaN)
         AniStop("MyCard Height " & Uuid)
         AniStop("MyCard Height " & Uuid)
         IsHeightAnimating = False
         IsHeightAnimating = False
     End Sub
     End Sub
@@ -281,26 +289,40 @@
     ''' <summary>
     ''' <summary>
     ''' 是否已被折叠。
     ''' 是否已被折叠。
     ''' </summary>
     ''' </summary>
-    Public Property IsSwaped As Boolean
+    Public Property IsSwapped As Boolean
         Get
         Get
-            Return _IsSwaped
+            Return _IsSwapped
         End Get
         End Get
         Set(value As Boolean)
         Set(value As Boolean)
-            If _IsSwaped = value Then Exit Property
-            _IsSwaped = value
-            If SwapControl Is Nothing Then Exit Property
+            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 事件中触发无动画的折叠,不需要在这里进行
             '若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行
-            If Not IsLoaded Then Exit Property
+            If Not IsLoaded Then Return
             '更新高度
             '更新高度
             SwapControl.Visibility = Visibility.Visible
             SwapControl.Visibility = Visibility.Visible
             TriggerForceResize()
             TriggerForceResize()
             '改变箭头
             '改变箭头
-            AniStart(AaRotateTransform(MainSwap, If(_IsSwaped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 400,, New AniEaseOutBack(AniEasePower.Weak)), "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 _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 Set
     End Property
     End Property
-    Private _IsSwaped As Boolean = False
+    Public Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False,
+    Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean)))
 
 
     Public Property SwapLogoRight As Boolean = False
     Public Property SwapLogoRight As Boolean = False
     Private IsMouseDown As Boolean = False
     Private IsMouseDown As Boolean = False
@@ -309,8 +331,8 @@
     Public Const SwapedHeight As Integer = 40
     Public Const SwapedHeight As Integer = 40
     Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
     Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
         Dim Pos As Double = Mouse.GetPosition(Me).Y
         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 Exit Sub '检测点击位置;或已经不在可视树上的误判
+        If Not IsSwapped AndAlso
+            (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
         IsMouseDown = True
         IsMouseDown = True
     End Sub
     End Sub
     Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
     Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
@@ -318,8 +340,8 @@
         IsMouseDown = False
         IsMouseDown = False
 
 
         Dim Pos As Double = Mouse.GetPosition(Me).Y
         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 Exit Sub '检测点击位置;或已经不在可视树上的误判
+        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)
         Dim ee = New RouteEventArgs(True)
         RaiseEvent PreviewSwap(Me, ee)
         RaiseEvent PreviewSwap(Me, ee)
@@ -328,8 +350,8 @@
             Return
             Return
         End If
         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)
         RaiseEvent Swap(Me, ee)
     End Sub
     End Sub
     Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
     Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
@@ -350,7 +372,7 @@ Partial Public Module ModAnimation
                 AaCode(
                 AaCode(
                 Sub()
                 Sub()
                     If RemoveFromChildren Then
                     If RemoveFromChildren Then
-                        If Control.Parent Is Nothing Then Exit Sub
+                        If Control.Parent Is Nothing Then Return
                         CType(Control.Parent, Object).Children.Remove(Control)
                         CType(Control.Parent, Object).Children.Remove(Control)
                     Else
                     Else
                         Control.Visibility = Visibility.Collapsed
                         Control.Visibility = Visibility.Collapsed
@@ -360,7 +382,7 @@ Partial Public Module ModAnimation
             }, "MyCard Dispose " & Control.Uuid)
             }, "MyCard Dispose " & Control.Uuid)
         Else
         Else
             If RemoveFromChildren Then
             If RemoveFromChildren Then
-                If Control.Parent Is Nothing Then Exit Sub
+                If Control.Parent Is Nothing Then Return
                 CType(Control.Parent, Object).Children.Remove(Control)
                 CType(Control.Parent, Object).Children.Remove(Control)
             Else
             Else
                 Control.Visibility = Visibility.Collapsed
                 Control.Visibility = Visibility.Collapsed

+ 6 - 6
Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb

@@ -40,7 +40,7 @@ Public Class MyCheckBox
     ''' <param name="user">是否由用户引发。</param>
     ''' <param name="user">是否由用户引发。</param>
     Public Sub SetChecked(value As Boolean, user As Boolean)
     Public Sub SetChecked(value As Boolean, user As Boolean)
         Try
         Try
-            If value = Checked Then Exit Sub
+            If value = Checked Then Return
 
 
             'Preview 事件
             'Preview 事件
             If value AndAlso user Then
             If value AndAlso user Then
@@ -50,7 +50,7 @@ Public Class MyCheckBox
                     MouseDowned = True
                     MouseDowned = True
                     Checkbox_MouseLeave()
                     Checkbox_MouseLeave()
                     MouseDowned = False
                     MouseDowned = False
-                    Exit Sub
+                    Return
                 End If
                 End If
             End If
             End If
 
 
@@ -133,14 +133,14 @@ Public Class MyCheckBox
     Private MouseDowned As Boolean = False
     Private MouseDowned As Boolean = False
     Private AllowMouseDown As Boolean = True
     Private AllowMouseDown As Boolean = True
     Private Sub Checkbox_MouseUp() Handles Me.MouseLeftButtonUp
     Private Sub Checkbox_MouseUp() Handles Me.MouseLeftButtonUp
-        If Not MouseDowned Then Exit Sub
+        If Not MouseDowned Then Return
         Log("[Control] 按下复选框(" & (Not Checked).ToString & "):" & Text)
         Log("[Control] 按下复选框(" & (Not Checked).ToString & "):" & Text)
         MouseDowned = False
         MouseDowned = False
         SetChecked(Not Checked, True)
         SetChecked(Not Checked, True)
         AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
         AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
     End Sub
     End Sub
     Private Sub Checkbox_MouseDown() Handles Me.MouseLeftButtonDown
     Private Sub Checkbox_MouseDown() Handles Me.MouseLeftButtonDown
-        If Not AllowMouseDown Then Exit Sub
+        If Not AllowMouseDown Then Return
         MouseDowned = True
         MouseDowned = True
         Focus()
         Focus()
         AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushBg1", 100), "MyCheckBox Background " & Uuid)
         AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushBg1", 100), "MyCheckBox Background " & Uuid)
@@ -154,7 +154,7 @@ Public Class MyCheckBox
         End If
         End If
     End Sub
     End Sub
     Private Sub Checkbox_MouseLeave() Handles Me.MouseLeave
     Private Sub Checkbox_MouseLeave() Handles Me.MouseLeave
-        If Not MouseDowned Then Exit Sub
+        If Not MouseDowned Then Return
         MouseDowned = False
         MouseDowned = False
         AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
         AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
         If Checked Then
         If Checked Then
@@ -203,7 +203,7 @@ Public Class MyCheckBox
          }, "MyCheckBox BorderColor " & Uuid)
          }, "MyCheckBox BorderColor " & Uuid)
     End Sub
     End Sub
     Private Sub Checkbox_MouseLeaveAnimation() Handles Me.MouseLeave
     Private Sub Checkbox_MouseLeaveAnimation() Handles Me.MouseLeave
-        If Not IsEnabled Then Exit Sub 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
+        If Not IsEnabled Then Return 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
         AniStart({
         AniStart({
                  AaColor(LabText, TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut)
                  AaColor(LabText, TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut)
          }, "MyCheckBox TextColor " & Uuid)
          }, "MyCheckBox TextColor " & Uuid)

+ 2 - 2
Plain Craft Launcher 2/Controls/MyComboBox.vb

@@ -8,7 +8,7 @@
     Public Property HintText As String = ""
     Public Property HintText As String = ""
     Public Overrides Sub OnApplyTemplate()
     Public Overrides Sub OnApplyTemplate()
         MyBase.OnApplyTemplate()
         MyBase.OnApplyTemplate()
-        If Not IsEditable Then Exit Sub
+        If Not IsEditable Then Return
         Try
         Try
             TextBox = Template.FindName("PART_EditableTextBox", Me)
             TextBox = Template.FindName("PART_EditableTextBox", Me)
             TextBox.AddHandler(LostFocusEvent, New RoutedEventHandler(AddressOf RefreshColor))
             TextBox.AddHandler(LostFocusEvent, New RoutedEventHandler(AddressOf RefreshColor))
@@ -116,7 +116,7 @@
     '修复 WPF Bug:下拉框文本修改后,依然误认为还选择着此前的选项,导致再次点击该选项时内容不变
     '修复 WPF Bug:下拉框文本修改后,依然误认为还选择着此前的选项,导致再次点击该选项时内容不变
     Private IsTextChanging As Boolean = False
     Private IsTextChanging As Boolean = False
     Private Sub MyComboBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
     Private Sub MyComboBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
-        If IsTextChanging OrElse Not IsEditable Then Exit Sub
+        If IsTextChanging OrElse Not IsEditable Then Return
         If SelectedItem IsNot Nothing AndAlso Text <> SelectedItem.ToString Then
         If SelectedItem IsNot Nothing AndAlso Text <> SelectedItem.ToString Then
             Dim RawText As String = Text
             Dim RawText As String = Text
             Dim RawSelectionStart As Integer = TextBox.SelectionStart
             Dim RawSelectionStart As Integer = TextBox.SelectionStart

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

@@ -35,7 +35,7 @@
             NewFontOpacity = 0.4
             NewFontOpacity = 0.4
             Time = AnimationTimeOut
             Time = AnimationTimeOut
         End If
         End If
-        If BackColorName = NewBackColorName AndAlso FontOpacity = NewFontOpacity Then Exit Sub
+        If BackColorName = NewBackColorName AndAlso FontOpacity = NewFontOpacity Then Return
         BackColorName = NewBackColorName
         BackColorName = NewBackColorName
         FontOpacity = NewFontOpacity
         FontOpacity = NewFontOpacity
         '触发颜色动画
         '触发颜色动画

+ 66 - 41
Plain Craft Launcher 2/Modules/Reference/SystemDropShadowChrome.vb → Plain Craft Launcher 2/Controls/MyDropShadow.vb

@@ -1,65 +1,89 @@
-Public NotInheritable Class SystemDropShadowChrome
+Public Class MyDropShadow
     Inherits Decorator
     Inherits Decorator
 
 
-    '源码来源于:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1
-
-    Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(SystemDropShadowChrome), New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
+    ''' <summary>
+    ''' 阴影颜色。
+    ''' </summary>
     Public Property Color As Color
     Public Property Color As Color
         Get
         Get
-            Return CType(GetValue(ColorProperty), Color)
+            Return GetValue(ColorProperty)
         End Get
         End Get
         Set(value As Color)
         Set(value As Color)
             SetValue(ColorProperty, value)
             SetValue(ColorProperty, value)
         End Set
         End Set
     End Property
     End Property
+    Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(MyDropShadow),
+        New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
+
+    ''' <summary>
+    ''' 阴影模糊半径。
+    ''' </summary>
+    Public Property ShadowRadius As Double
+        Get
+            Return GetValue(ShadowRadiusProperty)
+        End Get
+        Set(value As Double)
+            SetValue(ShadowRadiusProperty, value)
+        End Set
+    End Property
+    Public Shared ReadOnly ShadowRadiusProperty As DependencyProperty = DependencyProperty.Register("ShadowRadius", GetType(Double), GetType(MyDropShadow),
+        New FrameworkPropertyMetadata(CDbl(5), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
 
 
-    Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(SystemDropShadowChrome), New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid))
+    ''' <summary>
+    ''' 圆角大小。
+    ''' </summary>
     Public Property CornerRadius As CornerRadius
     Public Property CornerRadius As CornerRadius
         Get
         Get
-            Return CType(GetValue(CornerRadiusProperty), CornerRadius)
+            Return GetValue(CornerRadiusProperty)
         End Get
         End Get
         Set(value As CornerRadius)
         Set(value As CornerRadius)
             SetValue(CornerRadiusProperty, value)
             SetValue(CornerRadiusProperty, value)
         End Set
         End Set
     End Property
     End Property
     Private Shared Function IsCornerRadiusValid(value As Object) As Boolean
     Private Shared Function IsCornerRadiusValid(value As Object) As Boolean
-        Dim cr As CornerRadius = CType(value, CornerRadius)
+        Dim cr As CornerRadius = value
         Return Not (cr.TopLeft < 0.0 OrElse cr.TopRight < 0.0 OrElse cr.BottomLeft < 0.0 OrElse cr.BottomRight < 0.0 OrElse Double.IsNaN(cr.TopLeft) OrElse Double.IsNaN(cr.TopRight) OrElse Double.IsNaN(cr.BottomLeft) OrElse Double.IsNaN(cr.BottomRight) OrElse Double.IsInfinity(cr.TopLeft) OrElse Double.IsInfinity(cr.TopRight) OrElse Double.IsInfinity(cr.BottomLeft) OrElse Double.IsInfinity(cr.BottomRight))
         Return Not (cr.TopLeft < 0.0 OrElse cr.TopRight < 0.0 OrElse cr.BottomLeft < 0.0 OrElse cr.BottomRight < 0.0 OrElse Double.IsNaN(cr.TopLeft) OrElse Double.IsNaN(cr.TopRight) OrElse Double.IsNaN(cr.BottomLeft) OrElse Double.IsNaN(cr.BottomRight) OrElse Double.IsInfinity(cr.TopLeft) OrElse Double.IsInfinity(cr.TopRight) OrElse Double.IsInfinity(cr.BottomLeft) OrElse Double.IsInfinity(cr.BottomRight))
     End Function
     End Function
+    Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(MyDropShadow),
+        New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid))
+
+
+    ' =======================================
+    '  渲染
+    ' =======================================
 
 
-    Private Const ShadowDepth As Double = 5
 
 
     Protected Overrides Sub OnRender(drawingContext As DrawingContext)
     Protected Overrides Sub OnRender(drawingContext As DrawingContext)
         Dim cornerRadius As CornerRadius = Me.CornerRadius
         Dim cornerRadius As CornerRadius = Me.CornerRadius
-        Dim shadowBounds As New Rect(New Point(ShadowDepth, ShadowDepth), New Size(RenderSize.Width, RenderSize.Height))
+        Dim shadowBounds As New Rect(0, 0, RenderSize.Width, RenderSize.Height)
         Dim color As Color = Me.Color
         Dim color As Color = Me.Color
 
 
         If shadowBounds.Width > 0 AndAlso shadowBounds.Height > 0 AndAlso color.A > 0 Then
         If shadowBounds.Width > 0 AndAlso shadowBounds.Height > 0 AndAlso color.A > 0 Then
-            Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowDepth
-            Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowDepth
+            Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowRadius
+            Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowRadius
             Dim maxRadius As Double = Math.Min(centerWidth * 0.5, centerHeight * 0.5)
             Dim maxRadius As Double = Math.Min(centerWidth * 0.5, centerHeight * 0.5)
             cornerRadius.TopLeft = Math.Min(cornerRadius.TopLeft, maxRadius)
             cornerRadius.TopLeft = Math.Min(cornerRadius.TopLeft, maxRadius)
             cornerRadius.TopRight = Math.Min(cornerRadius.TopRight, maxRadius)
             cornerRadius.TopRight = Math.Min(cornerRadius.TopRight, maxRadius)
             cornerRadius.BottomLeft = Math.Min(cornerRadius.BottomLeft, maxRadius)
             cornerRadius.BottomLeft = Math.Min(cornerRadius.BottomLeft, maxRadius)
             cornerRadius.BottomRight = Math.Min(cornerRadius.BottomRight, maxRadius)
             cornerRadius.BottomRight = Math.Min(cornerRadius.BottomRight, maxRadius)
             Dim brushes As Brush() = GetBrushes(color, cornerRadius)
             Dim brushes As Brush() = GetBrushes(color, cornerRadius)
-            Dim centerTop As Double = shadowBounds.Top + ShadowDepth
-            Dim centerLeft As Double = shadowBounds.Left + ShadowDepth
-            Dim centerRight As Double = shadowBounds.Right - ShadowDepth
-            Dim centerBottom As Double = shadowBounds.Bottom - ShadowDepth
+            Dim centerTop As Double = shadowBounds.Top + ShadowRadius
+            Dim centerLeft As Double = shadowBounds.Left + ShadowRadius
+            Dim centerRight As Double = shadowBounds.Right - ShadowRadius
+            Dim centerBottom As Double = shadowBounds.Bottom - ShadowRadius
             Dim guidelineSetX As Double() = New Double() {centerLeft, centerLeft + cornerRadius.TopLeft, centerRight - cornerRadius.TopRight, centerLeft + cornerRadius.BottomLeft, centerRight - cornerRadius.BottomRight, centerRight}
             Dim guidelineSetX As Double() = New Double() {centerLeft, centerLeft + cornerRadius.TopLeft, centerRight - cornerRadius.TopRight, centerLeft + cornerRadius.BottomLeft, centerRight - cornerRadius.BottomRight, centerRight}
             Dim guidelineSetY As Double() = New Double() {centerTop, centerTop + cornerRadius.TopLeft, centerTop + cornerRadius.TopRight, centerBottom - cornerRadius.BottomLeft, centerBottom - cornerRadius.BottomRight, centerBottom}
             Dim guidelineSetY As Double() = New Double() {centerTop, centerTop + cornerRadius.TopLeft, centerTop + cornerRadius.TopRight, centerBottom - cornerRadius.BottomLeft, centerBottom - cornerRadius.BottomRight, centerBottom}
             drawingContext.PushGuidelineSet(New GuidelineSet(guidelineSetX, guidelineSetY))
             drawingContext.PushGuidelineSet(New GuidelineSet(guidelineSetX, guidelineSetY))
-            cornerRadius.TopLeft += ShadowDepth
-            cornerRadius.TopRight += ShadowDepth
-            cornerRadius.BottomLeft += ShadowDepth
-            cornerRadius.BottomRight += ShadowDepth
+            cornerRadius.TopLeft += ShadowRadius
+            cornerRadius.TopRight += ShadowRadius
+            cornerRadius.BottomLeft += ShadowRadius
+            cornerRadius.BottomRight += ShadowRadius
             Dim topLeft As New Rect(shadowBounds.Left, shadowBounds.Top, cornerRadius.TopLeft, cornerRadius.TopLeft)
             Dim topLeft As New Rect(shadowBounds.Left, shadowBounds.Top, cornerRadius.TopLeft, cornerRadius.TopLeft)
             drawingContext.DrawRectangle(brushes(Placement.TopLeft), Nothing, topLeft)
             drawingContext.DrawRectangle(brushes(Placement.TopLeft), Nothing, topLeft)
             Dim topWidth As Double = guidelineSetX(2) - guidelineSetX(1)
             Dim topWidth As Double = guidelineSetX(2) - guidelineSetX(1)
 
 
             If topWidth > 0 Then
             If topWidth > 0 Then
-                Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowDepth)
+                Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowRadius)
                 drawingContext.DrawRectangle(brushes(Placement.Top), Nothing, top)
                 drawingContext.DrawRectangle(brushes(Placement.Top), Nothing, top)
             End If
             End If
 
 
@@ -68,14 +92,14 @@
             Dim leftHeight As Double = guidelineSetY(3) - guidelineSetY(1)
             Dim leftHeight As Double = guidelineSetY(3) - guidelineSetY(1)
 
 
             If leftHeight > 0 Then
             If leftHeight > 0 Then
-                Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowDepth, leftHeight)
+                Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowRadius, leftHeight)
                 drawingContext.DrawRectangle(brushes(Placement.Left), Nothing, left)
                 drawingContext.DrawRectangle(brushes(Placement.Left), Nothing, left)
             End If
             End If
 
 
             Dim rightHeight As Double = guidelineSetY(4) - guidelineSetY(2)
             Dim rightHeight As Double = guidelineSetY(4) - guidelineSetY(2)
 
 
             If rightHeight > 0 Then
             If rightHeight > 0 Then
-                Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowDepth, rightHeight)
+                Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowRadius, rightHeight)
                 drawingContext.DrawRectangle(brushes(Placement.Right), Nothing, right)
                 drawingContext.DrawRectangle(brushes(Placement.Right), Nothing, right)
             End If
             End If
 
 
@@ -84,20 +108,20 @@
             Dim bottomWidth As Double = guidelineSetX(4) - guidelineSetX(3)
             Dim bottomWidth As Double = guidelineSetX(4) - guidelineSetX(3)
 
 
             If bottomWidth > 0 Then
             If bottomWidth > 0 Then
-                Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowDepth)
+                Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowRadius)
                 drawingContext.DrawRectangle(brushes(Placement.Bottom), Nothing, bottom)
                 drawingContext.DrawRectangle(brushes(Placement.Bottom), Nothing, bottom)
             End If
             End If
 
 
             Dim bottomRight As New Rect(guidelineSetX(4), guidelineSetY(4), cornerRadius.BottomRight, cornerRadius.BottomRight)
             Dim bottomRight As New Rect(guidelineSetX(4), guidelineSetY(4), cornerRadius.BottomRight, cornerRadius.BottomRight)
             drawingContext.DrawRectangle(brushes(Placement.BottomRight), Nothing, bottomRight)
             drawingContext.DrawRectangle(brushes(Placement.BottomRight), Nothing, bottomRight)
 
 
-            If cornerRadius.TopLeft = ShadowDepth AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then
+            If cornerRadius.TopLeft = ShadowRadius AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then
                 Dim center As New Rect(guidelineSetX(0), guidelineSetY(0), centerWidth, centerHeight)
                 Dim center As New Rect(guidelineSetX(0), guidelineSetY(0), centerWidth, centerHeight)
                 drawingContext.DrawRectangle(brushes(Placement.Center), Nothing, center)
                 drawingContext.DrawRectangle(brushes(Placement.Center), Nothing, center)
             Else
             Else
                 Dim figure As New PathFigure()
                 Dim figure As New PathFigure()
 
 
-                If cornerRadius.TopLeft > ShadowDepth Then
+                If cornerRadius.TopLeft > ShadowRadius Then
                     figure.StartPoint = New Point(guidelineSetX(1), guidelineSetY(0))
                     figure.StartPoint = New Point(guidelineSetX(1), guidelineSetY(0))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(1), guidelineSetY(1)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(1), guidelineSetY(1)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(1)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(1)), True))
@@ -105,7 +129,7 @@
                     figure.StartPoint = New Point(guidelineSetX(0), guidelineSetY(0))
                     figure.StartPoint = New Point(guidelineSetX(0), guidelineSetY(0))
                 End If
                 End If
 
 
-                If cornerRadius.BottomLeft > ShadowDepth Then
+                If cornerRadius.BottomLeft > ShadowRadius Then
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(3)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(3)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(3)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(3)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(5)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(5)), True))
@@ -113,7 +137,7 @@
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(5)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(5)), True))
                 End If
                 End If
 
 
-                If cornerRadius.BottomRight > ShadowDepth Then
+                If cornerRadius.BottomRight > ShadowRadius Then
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(5)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(5)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(4)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(4)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(4)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(4)), True))
@@ -121,7 +145,7 @@
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(5)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(5)), True))
                 End If
                 End If
 
 
-                If cornerRadius.TopRight > ShadowDepth Then
+                If cornerRadius.TopRight > ShadowRadius Then
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(2)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(2)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(2)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(2)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(0)), True))
                     figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(0)), True))
@@ -158,28 +182,27 @@
     Private _brushes As Brush()
     Private _brushes As Brush()
 
 
     Private Shared Sub ClearBrushes(o As DependencyObject, e As DependencyPropertyChangedEventArgs)
     Private Shared Sub ClearBrushes(o As DependencyObject, e As DependencyPropertyChangedEventArgs)
-        CType(o, SystemDropShadowChrome)._brushes = Nothing
+        CType(o, MyDropShadow)._brushes = Nothing
     End Sub
     End Sub
-    Private Shared Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection
-        Dim gradientScale As Double = 1 / (cornerRadius + ShadowDepth)
-        Dim gsc As New GradientStopCollection From {
-            New GradientStop(c, (0.5 + cornerRadius) * gradientScale)
-        }
+    Private Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection
+        Dim gradientScale As Double = 1 / (ShadowRadius + cornerRadius)
+        Dim gsc As New GradientStopCollection
         Dim stopColor As Color = c
         Dim stopColor As Color = c
+        gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.1 + cornerRadius) * gradientScale))
         stopColor.A = CByte(0.74336 * c.A)
         stopColor.A = CByte(0.74336 * c.A)
-        gsc.Add(New GradientStop(stopColor, (1.5 + cornerRadius) * gradientScale))
+        gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.3 + cornerRadius) * gradientScale))
         stopColor.A = CByte(0.38053 * c.A)
         stopColor.A = CByte(0.38053 * c.A)
-        gsc.Add(New GradientStop(stopColor, (2.5 + cornerRadius) * gradientScale))
+        gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.5 + cornerRadius) * gradientScale))
         stopColor.A = CByte(0.12389 * c.A)
         stopColor.A = CByte(0.12389 * c.A)
-        gsc.Add(New GradientStop(stopColor, (3.5 + cornerRadius) * gradientScale))
+        gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.7 + cornerRadius) * gradientScale))
         stopColor.A = CByte(0.02654 * c.A)
         stopColor.A = CByte(0.02654 * c.A)
-        gsc.Add(New GradientStop(stopColor, (4.5 + cornerRadius) * gradientScale))
+        gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.9 + cornerRadius) * gradientScale))
         stopColor.A = 0
         stopColor.A = 0
-        gsc.Add(New GradientStop(stopColor, (5 + cornerRadius) * gradientScale))
+        gsc.Add(New GradientStop(stopColor, (ShadowRadius + cornerRadius) * gradientScale))
         gsc.Freeze()
         gsc.Freeze()
         Return gsc
         Return gsc
     End Function
     End Function
-    Private Shared Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush()
+    Private Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush()
         Dim brushes As Brush() = New Brush(8) {}
         Dim brushes As Brush() = New Brush(8) {}
         brushes(Placement.Center) = New SolidColorBrush(c)
         brushes(Placement.Center) = New SolidColorBrush(c)
         brushes(Placement.Center).Freeze()
         brushes(Placement.Center).Freeze()
@@ -297,3 +320,5 @@
     End Function
     End Function
 
 
 End Class
 End Class
+
+'参考自:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1

+ 5 - 5
Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb

@@ -11,7 +11,7 @@
             Return _Progress
             Return _Progress
         End Get
         End Get
         Set(value As Double)
         Set(value As Double)
-            If _Progress = value Then Exit Property
+            If _Progress = value Then Return
             _Progress = value
             _Progress = value
             If value < 0.0001 Then
             If value < 0.0001 Then
                 PanProgress.Visibility = Visibility.Collapsed
                 PanProgress.Visibility = Visibility.Collapsed
@@ -30,7 +30,7 @@
             Return _Logo
             Return _Logo
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If value = _Logo Then Exit Property
+            If value = _Logo Then Return
             _Logo = value
             _Logo = value
             Path.Data = (New GeometryConverter).ConvertFromString(value)
             Path.Data = (New GeometryConverter).ConvertFromString(value)
         End Set
         End Set
@@ -51,7 +51,7 @@
             Return _Show
             Return _Show
         End Get
         End Get
         Set(value As Boolean)
         Set(value As Boolean)
-            If _Show = value Then Exit Property
+            If _Show = value Then Return
             _Show = value
             _Show = value
             RunInUi(Sub()
             RunInUi(Sub()
                         If value Then
                         If value Then
@@ -121,7 +121,7 @@
         Focus()
         Focus()
     End Sub
     End Sub
     Private Sub Button_RightMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseRightButtonDown
     Private Sub Button_RightMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseRightButtonDown
-        If Not CanRightClick Then Exit Sub
+        If Not CanRightClick Then Return
         If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
         If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
             AniStart({
             AniStart({
                 AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
                 AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
@@ -141,7 +141,7 @@
         RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
         RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
     End Sub
     End Sub
     Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp
     Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp
-        If Not CanRightClick Then Exit Sub
+        If Not CanRightClick Then Return
         If Not IsLeftMouseHeld Then
         If Not IsLeftMouseHeld Then
             AniStart({
             AniStart({
                 AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
                 AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)

+ 2 - 2
Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb

@@ -14,7 +14,7 @@ Public Class MyExtraTextButton
             Return _Logo
             Return _Logo
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If value = _Logo Then Exit Property
+            If value = _Logo Then Return
             _Logo = value
             _Logo = value
             Path.Data = (New GeometryConverter).ConvertFromString(value)
             Path.Data = (New GeometryConverter).ConvertFromString(value)
         End Set
         End Set
@@ -55,7 +55,7 @@ Public Class MyExtraTextButton
             Return _Show
             Return _Show
         End Get
         End Get
         Set(value As Boolean)
         Set(value As Boolean)
-            If _Show = value Then Exit Property
+            If _Show = value Then Return
             _Show = value
             _Show = value
             RunInUi(
             RunInUi(
             Sub()
             Sub()

+ 4 - 12
Plain Craft Launcher 2/Controls/MyHint.xaml

@@ -3,22 +3,14 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="clr-namespace:PCL"
              xmlns:local="clr-namespace:PCL"
              FocusVisualStyle="{x:Null}"
              FocusVisualStyle="{x:Null}"
-             UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Stretch" BorderThickness="1" BorderBrush="#99FF4444" CornerRadius="3">
-    <Border.Background>
-        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
-            <GradientStop x:Name="Gradient1" Color="#88FFBBBB" Offset="0.3"/>
-            <GradientStop x:Name="Gradient2" Color="#88FF8888" Offset="0.9"/>
-        </LinearGradientBrush>
-    </Border.Background>
+             UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Stretch" BorderThickness="3,0,0,0" BorderBrush="#99FF4444" CornerRadius="2">
     <Grid UseLayoutRounding="False" SnapsToDevicePixels="False">
     <Grid UseLayoutRounding="False" SnapsToDevicePixels="False">
         <Grid.ColumnDefinitions>
         <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="Auto" />
             <ColumnDefinition Width="1*" />
             <ColumnDefinition Width="1*" />
             <ColumnDefinition Width="Auto" />
             <ColumnDefinition Width="Auto" />
         </Grid.ColumnDefinitions>
         </Grid.ColumnDefinitions>
-        <Path IsHitTestVisible="False" Grid.Column="0" x:Name="Path" VerticalAlignment="Center" Fill="#BF0000" Width="14" Stretch="Uniform" Margin="10,0,2,0" Data="F1 M 58.5832,55.4172L 17.4169,55.4171C 15.5619,53.5621 15.5619,50.5546 17.4168,48.6996L 35.201,15.8402C 37.056,13.9852 40.0635,13.9852 41.9185,15.8402L 58.5832,48.6997C 60.4382,50.5546 60.4382,53.5622 58.5832,55.4172 Z M 34.0417,25.7292L 36.0208,41.9584L 39.9791,41.9583L 41.9583,25.7292L 34.0417,25.7292 Z M 38,44.3333C 36.2511,44.3333 34.8333,45.7511 34.8333,47.5C 34.8333,49.2489 36.2511,50.6667 38,50.6667C 39.7489,50.6667 41.1666,49.2489 41.1666,47.5C 41.1666,45.7511 39.7489,44.3333 38,44.3333 Z " />
-        <TextBlock IsHitTestVisible="False" Grid.Column="1" x:Name="LabText" Margin="0,0.5,0,0" Padding="6,6,8,6" VerticalAlignment="Center" Foreground="#BF0000" TextWrapping="Wrap" />
-        <local:MyIconButton x:Name="BtnClose" SnapsToDevicePixels="True" Visibility="Collapsed" Foreground="#BF0000" Theme="Custom" Margin="0,0,6,0" Grid.Column="2" Height="20" Width="20" VerticalAlignment="Center" Logo="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z"
-                           ToolTip="不再显示" ToolTipService.Placement="Center" ToolTipService.VerticalOffset="29" ToolTipService.HorizontalOffset="2" />
+        <TextBlock IsHitTestVisible="False" x:Name="LabText" LineHeight="16" Margin="0,0,0,-1" Padding="12,9" VerticalAlignment="Center" TextWrapping="Wrap" />
+        <local:MyIconButton x:Name="BtnClose" Visibility="Collapsed" Theme="Custom" Margin="0,0,8,0" Grid.Column="1" Height="20" Width="20" VerticalAlignment="Center" Logo="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z"
+                           ToolTip="不再提示" ToolTipService.Placement="Center" ToolTipService.VerticalOffset="29" ToolTipService.HorizontalOffset="2" />
     </Grid>
     </Grid>
 </Border>
 </Border>

+ 73 - 38
Plain Craft Launcher 2/Controls/MyHint.xaml.vb

@@ -2,37 +2,73 @@
 
 
 <ContentProperty("Inlines")>
 <ContentProperty("Inlines")>
 Public Class MyHint
 Public Class MyHint
-
     Public Uuid As Integer = GetUuid()
     Public Uuid As Integer = GetUuid()
 
 
-    Private _IsWarn As Boolean = True
-    Public Property IsWarn As Boolean
+    '边框
+    Public Property HasBorder As Boolean
         Get
         Get
-            Return _IsWarn
+            Return BorderThickness.Top > 0
         End Get
         End Get
         Set(value As Boolean)
         Set(value As Boolean)
-            If _IsWarn = value Then Exit Property
-            _IsWarn = value
-            If _IsWarn Then
-                BorderBrush = New MyColor("#CCFF4444")
-                Gradient1.Color = New MyColor("#BBFFBBBB")
-                Gradient2.Color = New MyColor("#BBFF8888")
-                Path.Fill = New MyColor("#BF0000")
-                LabText.Foreground = New MyColor("#BF0000")
-                BtnClose.Foreground = New MyColor("#BF0000")
-                Path.Data = (New GeometryConverter).ConvertFromString("F1 M 58.5832,55.4172L 17.4169,55.4171C 15.5619,53.5621 15.5619,50.5546 17.4168,48.6996L 35.201,15.8402C 37.056,13.9852 40.0635,13.9852 41.9185,15.8402L 58.5832,48.6997C 60.4382,50.5546 60.4382,53.5622 58.5832,55.4172 Z M 34.0417,25.7292L 36.0208,41.9584L 39.9791,41.9583L 41.9583,25.7292L 34.0417,25.7292 Z M 38,44.3333C 36.2511,44.3333 34.8333,45.7511 34.8333,47.5C 34.8333,49.2489 36.2511,50.6667 38,50.6667C 39.7489,50.6667 41.1666,49.2489 41.1666,47.5C 41.1666,45.7511 39.7489,44.3333 38,44.3333 Z ")
+            If value Then
+                BorderThickness = New Thickness(3, GetWPFSize(1), GetWPFSize(1), GetWPFSize(1))
             Else
             Else
-                BorderBrush = New MyColor("#CC4D76FF")
-                Gradient1.Color = New MyColor("#BBB0D0FF")
-                Gradient2.Color = New MyColor("#BB9EBAFF")
-                Path.Fill = New MyColor("#0062BF")
-                LabText.Foreground = New MyColor("#0062BF")
-                BtnClose.Foreground = New MyColor("#0062BF")
-                Path.Data = (New GeometryConverter).ConvertFromString("F1M38,19C48.4934,19 57,27.5066 57,38 57,48.4934 48.4934,57 38,57 27.5066,57 19,48.4934 19,38 19,27.5066 27.5066,19 38,19z M33.25,33.25L33.25,36.4167 36.4166,36.4167 36.4166,47.5 33.25,47.5 33.25,50.6667 44.3333,50.6667 44.3333,47.5 41.1666,47.5 41.1666,36.4167 41.1666,33.25 33.25,33.25z M38.7917,25.3333C37.48,25.3333 36.4167,26.3967 36.4167,27.7083 36.4167,29.02 37.48,30.0833 38.7917,30.0833 40.1033,30.0833 41.1667,29.02 41.1667,27.7083 41.1667,26.3967 40.1033,25.3333 38.7917,25.3333z")
+                BorderThickness = New Thickness(3, 0, 0, 0)
             End If
             End If
         End Set
         End Set
     End Property
     End Property
 
 
+    '配色
+    Public Enum Themes
+        Blue = 0
+        Red = 1
+        Yellow = 2
+    End Enum
+    Public Property Theme As Themes
+        Get
+            Return _ColorType
+        End Get
+        Set(value As Themes)
+            _ColorType = value
+            UpdateUI()
+        End Set
+    End Property
+    Private _ColorType As Themes = Themes.Red
+    Private Sub UpdateUI() Handles Me.Loaded
+        Select Case Theme
+            Case Themes.Blue
+                Background = New MyColor("#d9ecff")
+                BorderBrush = New MyColor("#1172D4")
+                LabText.Foreground = New MyColor("#0F64B8")
+                BtnClose.Foreground = New MyColor("#0F64B8")
+            Case Themes.Red
+                Background = New MyColor("#ffdddf")
+                BorderBrush = New MyColor("#d82929")
+                LabText.Foreground = New MyColor("#bf0b0b")
+                BtnClose.Foreground = New MyColor("#bf0b0b")
+            Case Themes.Yellow
+                Background = New MyColor("#ffebd7")
+                BorderBrush = New MyColor("#f57a00")
+                LabText.Foreground = New MyColor("#d86c00")
+                BtnClose.Foreground = New MyColor("#d86c00")
+        End Select
+    End Sub
+    <Obsolete("IsWarn 已过时。请换用 Theme 属性。")>
+    Public Property IsWarn As Boolean
+        Get
+            Return Theme = Themes.Red
+        End Get
+        Set(value As Boolean)
+            Theme = If(value, Themes.Red, Themes.Blue)
+        End Set
+    End Property
+    Public Shared ReadOnly IsWarnProperty As DependencyProperty =
+        DependencyProperty.Register("IsWarn", GetType(Boolean), GetType(MyHint), New PropertyMetadata(True,
+        Sub(d As MyHint, e As DependencyPropertyChangedEventArgs)
+            d.Theme = If(e.NewValue, Themes.Red, Themes.Blue)
+        End Sub))
+
+    '文本
     Public ReadOnly Property Inlines As InlineCollection
     Public ReadOnly Property Inlines As InlineCollection
         Get
         Get
             Return LabText.Inlines
             Return LabText.Inlines
@@ -52,6 +88,7 @@ Public Class MyHint
             d.LabText.Text = e.NewValue
             d.LabText.Text = e.NewValue
         End Sub))
         End Sub))
 
 
+    '关闭按钮
     Public Property CanClose As Boolean
     Public Property CanClose As Boolean
         Get
         Get
             Return BtnClose.Visibility = Visibility.Visible
             Return BtnClose.Visibility = Visibility.Visible
@@ -60,12 +97,9 @@ Public Class MyHint
             BtnClose.Visibility = If(value, Visibility.Visible, Visibility.Collapsed)
             BtnClose.Visibility = If(value, Visibility.Visible, Visibility.Collapsed)
         End Set
         End Set
     End Property
     End Property
-
     Public Property RelativeSetup As String = ""
     Public Property RelativeSetup As String = ""
     Private Sub MyHint_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
     Private Sub MyHint_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
-        If CanClose AndAlso Setup.Get(RelativeSetup) Then
-            Visibility = Visibility.Collapsed
-        End If
+        If CanClose AndAlso Setup.Get(RelativeSetup) Then Visibility = Visibility.Collapsed
     End Sub
     End Sub
     Private Sub BtnClose_Click(sender As Object, e As EventArgs) Handles BtnClose.Click
     Private Sub BtnClose_Click(sender As Object, e As EventArgs) Handles BtnClose.Click
         Setup.Set(RelativeSetup, True)
         Setup.Set(RelativeSetup, True)
@@ -121,20 +155,21 @@ Public Class MyHint
 End Class
 End Class
 Partial Public Module ModAnimation
 Partial Public Module ModAnimation
     Public Sub AniDispose(Control As MyHint, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
     Public Sub AniDispose(Control As MyHint, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
-        If Not Control.IsHitTestVisible Then Exit Sub
+        If Not Control.IsHitTestVisible Then Return
         Control.IsHitTestVisible = False
         Control.IsHitTestVisible = False
         AniStart({
         AniStart({
-                     AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
-                     AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
-                     AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
-                     AaCode(Sub()
-                                If RemoveFromChildren Then
-                                    CType(Control.Parent, Object).Children.Remove(Control)
-                                Else
-                                    Control.Visibility = Visibility.Collapsed
-                                End If
-                                If CallBack IsNot Nothing Then CallBack(Control)
-                            End Sub,, True)
-            }, "MyCard Dispose " & Control.Uuid)
+            AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
+            AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
+            AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
+            AaCode(
+            Sub()
+                If RemoveFromChildren Then
+                    CType(Control.Parent, Object).Children.Remove(Control)
+                Else
+                    Control.Visibility = Visibility.Collapsed
+                End If
+                If CallBack IsNot Nothing Then CallBack(Control)
+            End Sub,, True)
+        }, "MyCard Dispose " & Control.Uuid)
     End Sub
     End Sub
 End Module
 End Module

+ 1 - 1
Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb

@@ -198,7 +198,7 @@
 End Class
 End Class
 Partial Public Module ModAnimation
 Partial Public Module ModAnimation
     Public Sub AniDispose(Control As MyIconButton, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
     Public Sub AniDispose(Control As MyIconButton, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
-        If Not Control.IsHitTestVisible Then Exit Sub
+        If Not Control.IsHitTestVisible Then Return
         Control.IsHitTestVisible = False
         Control.IsHitTestVisible = False
         AniStart({
         AniStart({
                  AaScaleTransform(Control, -1.5, 200,, New AniEaseInFluent),
                  AaScaleTransform(Control, -1.5, 200,, New AniEaseInFluent),

+ 1 - 1
Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb

@@ -72,7 +72,7 @@ Public Class MyIconTextButton
     Public Event Click(sender As Object, e As RouteEventArgs)
     Public Event Click(sender As Object, e As RouteEventArgs)
     Private IsMouseDown As Boolean = False
     Private IsMouseDown As Boolean = False
     Private Sub MyIconTextButton_MouseUp() Handles Me.MouseLeftButtonUp
     Private Sub MyIconTextButton_MouseUp() Handles Me.MouseLeftButtonUp
-        If Not IsMouseDown Then Exit Sub
+        If Not IsMouseDown Then Return
         Log("[Control] 按下带图标按钮:" & Text)
         Log("[Control] 按下带图标按钮:" & Text)
         IsMouseDown = False
         IsMouseDown = False
         RaiseEvent Click(Me, New RouteEventArgs(True))
         RaiseEvent Click(Me, New RouteEventArgs(True))

+ 18 - 57
Plain Craft Launcher 2/Controls/MyImage.vb

@@ -25,7 +25,7 @@
 
 
     ''' <summary>
     ''' <summary>
     ''' 与 Image 的 Source 类似。
     ''' 与 Image 的 Source 类似。
-    ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。
+    ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示;若 EnableCache 设为 True,图片还会保存为本地缓存。
     ''' 支持 WebP 格式的图片。
     ''' 支持 WebP 格式的图片。
     ''' </summary>
     ''' </summary>
     Public Shadows Property Source As String '覆写 Image 的 Source 属性
     Public Shadows Property Source As String '覆写 Image 的 Source 属性
@@ -34,9 +34,9 @@
         End Get
         End Get
         Set(value As String)
         Set(value As String)
             If value = "" Then value = Nothing
             If value = "" Then value = Nothing
-            If _Source = value Then Exit Property
+            If _Source = value Then Return
             _Source = value
             _Source = value
-            If Not IsInitialized Then Exit Property '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
+            If Not IsInitialized Then Return '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
             Load()
             Load()
         End Set
         End Set
     End Property
     End Property
@@ -46,7 +46,7 @@
     Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
     Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
 
 
     ''' <summary>
     ''' <summary>
-    ''' 当 Source 首次下载失败时,会从该备用地址加载图片。
+    ''' 若 Source 是一个网络图片,该地址将作为第二下载源。
     ''' </summary>
     ''' </summary>
     Public Property FallbackSource As String
     Public Property FallbackSource As String
         Get
         Get
@@ -82,7 +82,7 @@
         End Get
         End Get
         Set(value As String)
         Set(value As String)
             If value = "" Then value = Nothing
             If value = "" Then value = Nothing
-            If _ActualSource = value Then Exit Property
+            If _ActualSource = value Then Return
             _ActualSource = value
             _ActualSource = value
             Try
             Try
                 Dim Bitmap As MyBitmap = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件
                 Dim Bitmap As MyBitmap = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件
@@ -103,82 +103,43 @@
         '空
         '空
         If Source Is Nothing Then
         If Source Is Nothing Then
             ActualSource = Nothing
             ActualSource = Nothing
-            Exit Sub
+            Return
         End If
         End If
         '本地图片
         '本地图片
         If Not Source.StartsWithF("http") Then
         If Not Source.StartsWithF("http") Then
             ActualSource = Source
             ActualSource = Source
-            Exit Sub
+            Return
         End If
         End If
         '从缓存加载网络图片
         '从缓存加载网络图片
-        Dim Url As String = Source
-        Dim Retried As Boolean = False
-        Dim TempPath As String = GetTempPath(Url)
-        Dim TempFile As New FileInfo(TempPath)
         Dim EnableCache As Boolean = Me.EnableCache
         Dim EnableCache As Boolean = Me.EnableCache
+        Dim TempPath As String = GetTempPath(Source) & If(EnableCache, "", GetUuid()) '不启用缓存时加上随机字符串,避免冲突
+        Dim TempFile As New FileInfo(TempPath)
         If EnableCache AndAlso TempFile.Exists Then
         If EnableCache AndAlso TempFile.Exists Then
             ActualSource = TempPath
             ActualSource = TempPath
-            If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
+            If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Return '无需刷新缓存
         End If
         End If
         RunInNewThread(
         RunInNewThread(
         Sub()
         Sub()
-            Dim TempDownloadingPath As String = Nothing
             Try
             Try
-RetryStart:
                 '下载
                 '下载
-                ActualSource = LoadingSource '显示加载中图片
-                TempDownloadingPath = TempPath & RandomInteger(0, 10000000)
-                Directory.CreateDirectory(GetPathFromFullPath(TempPath)) '重新实现下载,以避免携带 Header(#5072)
-                Using Client As New Net.WebClient
-                    Client.DownloadFile(Url, TempDownloadingPath)
-                End Using
-                If Url <> Source AndAlso Url <> FallbackSource Then
-                    '已经更换了地址
-                    File.Delete(TempDownloadingPath)
-                ElseIf EnableCache Then
+                ActualSource = LoadingSource '显示加载中的占位图片
+                NetDownloadByLoader(
+                    If(String.IsNullOrEmpty(FallbackSource), {Source}, {Source, FallbackSource}),
+                    TempPath, SimulateBrowserHeaders:=True)
+                If EnableCache Then
                     '保存缓存并显示
                     '保存缓存并显示
-                    If File.Exists(TempPath) Then File.Delete(TempPath)
-                    FileSystem.Rename(TempDownloadingPath, TempPath)
                     RunInUi(Sub() ActualSource = TempPath)
                     RunInUi(Sub() ActualSource = TempPath)
                 Else
                 Else
                     '直接显示
                     '直接显示
-                    RunInUiWait(Sub() ActualSource = TempDownloadingPath)
-                    File.Delete(TempDownloadingPath)
+                    RunInUiWait(Sub() ActualSource = TempPath)
+                    File.Delete(TempPath)
                 End If
                 End If
             Catch ex As Exception
             Catch ex As Exception
                 Try
                 Try
                     If TempPath IsNot Nothing Then File.Delete(TempPath)
                     If TempPath IsNot Nothing Then File.Delete(TempPath)
-                    If TempDownloadingPath IsNot Nothing Then File.Delete(TempDownloadingPath)
                 Catch
                 Catch
                 End Try
                 End Try
-                If Not Retried Then
-                    '更换备用地址
-                    Log(ex, $"下载图片可重试地失败({Url})", LogLevel.Developer)
-                    Retried = True
-                    Url = If(FallbackSource, Source)
-                    '空
-                    If Url Is Nothing Then
-                        ActualSource = Nothing
-                        Exit Sub
-                    End If
-                    '本地图片
-                    If Not Url.StartsWithF("http") Then
-                        ActualSource = Url
-                        Exit Sub
-                    End If
-                    '从缓存加载网络图片
-                    TempPath = GetTempPath(Url)
-                    TempFile = New FileInfo(TempPath)
-                    If EnableCache AndAlso TempFile.Exists() Then
-                        ActualSource = TempPath
-                        If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
-                    End If
-                    '下载
-                    If Source = Url Then Thread.Sleep(1000) '延迟 1s 重试
-                    GoTo RetryStart
-                Else
-                    Log(ex, $"下载图片失败({Url})", LogLevel.Hint)
-                End If
+                Log(ex, $"下载图片失败", LogLevel.Hint)
             End Try
             End Try
         End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
         End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
     End Sub
     End Sub

+ 13 - 13
Plain Craft Launcher 2/Controls/MyListItem.xaml.vb

@@ -197,7 +197,7 @@ Public Class MyListItem
             Return _Info
             Return _Info
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If _Info = value Then Exit Property
+            If _Info = value Then Return
             value = value.Replace(vbCr, "").Replace(vbLf, "")
             value = value.Replace(vbCr, "").Replace(vbLf, "")
             _Info = value
             _Info = value
             LabInfo.Text = value
             LabInfo.Text = value
@@ -212,7 +212,7 @@ Public Class MyListItem
             Return _Logo
             Return _Logo
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If _Logo = value Then Exit Property
+            If _Logo = value Then Return
             _Logo = value
             _Logo = value
             '删除旧 Logo
             '删除旧 Logo
             If Not IsNothing(PathLogo) Then Children.Remove(PathLogo)
             If Not IsNothing(PathLogo) Then Children.Remove(PathLogo)
@@ -297,7 +297,7 @@ Public Class MyListItem
             Return _Type
             Return _Type
         End Get
         End Get
         Set(value As CheckType)
         Set(value As CheckType)
-            If _Type = value Then Exit Property
+            If _Type = value Then Return
             _Type = value
             _Type = value
             '切换左栏大小
             '切换左栏大小
             ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6))
             ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6))
@@ -345,7 +345,7 @@ Public Class MyListItem
             Return _Checked
             Return _Checked
         End Get
         End Get
         Set(value As Boolean)
         Set(value As Boolean)
-            SetChecked(value, False, True)
+            SetChecked(value, False, value <> _Checked) '仅在值发生变化时触发动画 (#4596)
         End Set
         End Set
     End Property
     End Property
     ''' <summary>
     ''' <summary>
@@ -367,31 +367,31 @@ Public Class MyListItem
                     RaiseEvent Changed(Me, ChangedEventArgs)
                     RaiseEvent Changed(Me, ChangedEventArgs)
                     If ChangedEventArgs.Handled Then
                     If ChangedEventArgs.Handled Then
                         _Checked = RawValue
                         _Checked = RawValue
-                        Exit Sub
+                        Return
                     End If
                     End If
                 End If
                 End If
                 _Checked = value
                 _Checked = value
             Else
             Else
-                If value = _Checked Then Exit Sub
+                If value = _Checked Then Return
                 _Checked = value
                 _Checked = value
                 If IsInitialized Then
                 If IsInitialized Then
                     RaiseEvent Changed(Me, ChangedEventArgs)
                     RaiseEvent Changed(Me, ChangedEventArgs)
                     If ChangedEventArgs.Handled Then
                     If ChangedEventArgs.Handled Then
                         _Checked = RawValue
                         _Checked = RawValue
-                        Exit Sub
+                        Return
                     End If
                     End If
                 End If
                 End If
             End If
             End If
             If value Then
             If value Then
                 Dim CheckEventArgs As New RouteEventArgs(user)
                 Dim CheckEventArgs As New RouteEventArgs(user)
                 RaiseEvent Check(Me, CheckEventArgs)
                 RaiseEvent Check(Me, CheckEventArgs)
-                If CheckEventArgs.Handled Then Exit Sub
+                If CheckEventArgs.Handled Then Return
             End If
             End If
 
 
             '保证只有一个单选 ListItem 选中
             '保证只有一个单选 ListItem 选中
 
 
             If Type = CheckType.RadioBox Then
             If Type = CheckType.RadioBox Then
-                If IsNothing(Parent) Then Exit Sub
+                If IsNothing(Parent) Then Return
                 Dim RadioboxList As New List(Of MyListItem)
                 Dim RadioboxList As New List(Of MyListItem)
                 Dim CheckedCount As Integer = 0
                 Dim CheckedCount As Integer = 0
                 '收集控件列表与选中个数
                 '收集控件列表与选中个数
@@ -502,15 +502,15 @@ Public Class MyListItem
 
 
     '触发点击事件
     '触发点击事件
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
-        If Not IsMouseDown Then Exit Sub
+        If Not IsMouseDown Then Return
         RaiseEvent Click(sender, e)
         RaiseEvent Click(sender, e)
-        If e.Handled Then Exit Sub
+        If e.Handled Then Return
         '触发自定义事件
         '触发自定义事件
         If Not String.IsNullOrEmpty(EventType) Then
         If Not String.IsNullOrEmpty(EventType) Then
             ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents)
             ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents)
             e.Handled = True
             e.Handled = True
         End If
         End If
-        If e.Handled Then Exit Sub
+        If e.Handled Then Return
         '实际的单击处理
         '实际的单击处理
         Select Case Type
         Select Case Type
             Case CheckType.Clickable
             Case CheckType.Clickable
@@ -593,7 +593,7 @@ Public Class MyListItem
                 Time = 180
                 Time = 180
             End If
             End If
         End If
         End If
-        If StateLast = StateNew Then Exit Sub
+        If StateLast = StateNew Then Return
         StateLast = StateNew
         StateLast = StateNew
         '触发颜色动画
         '触发颜色动画
         If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
         If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画

+ 6 - 10
Plain Craft Launcher 2/Controls/MyLoading.xaml.vb

@@ -35,7 +35,7 @@ Public Class MyLoading
             Return _ShowProgress
             Return _ShowProgress
         End Get
         End Get
         Set(value As Boolean)
         Set(value As Boolean)
-            If _ShowProgress = value Then Exit Property
+            If _ShowProgress = value Then Return
             _ShowProgress = value
             _ShowProgress = value
             RefreshText()
             RefreshText()
         End Set
         End Set
@@ -79,11 +79,7 @@ Public Class MyLoading
                         Do While Ex.InnerException IsNot Nothing
                         Do While Ex.InnerException IsNot Nothing
                             Ex = Ex.InnerException
                             Ex = Ex.InnerException
                         Loop
                         Loop
-                        LabText.Text = StrTrim(Ex.Message)
-                        If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
-                            "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then
-                            LabText.Text = "网络环境不佳,请重试或尝试使用 VPN"
-                        End If
+                        LabText.Text = If(Ex.IsNetworkRelated(), "网络环境不佳,请稍后再试,或使用 VPN 改善网络环境", StrTrim(Ex.Message))
                     End If
                     End If
                 Else
                 Else
                     LabText.Text = TextError
                     LabText.Text = TextError
@@ -142,7 +138,7 @@ Public Class MyLoading
             Return _OuterState
             Return _OuterState
         End Get
         End Get
         Set(value As MyLoadingState)
         Set(value As MyLoadingState)
-            If _OuterState = value Then Exit Property
+            If _OuterState = value Then Return
             Dim OldValue = _OuterState
             Dim OldValue = _OuterState
             _OuterState = value
             _OuterState = value
             '引发事件
             '引发事件
@@ -159,7 +155,7 @@ Public Class MyLoading
             Return _InnerState
             Return _InnerState
         End Get
         End Get
         Set(value As MyLoadingState)
         Set(value As MyLoadingState)
-            If _InnerState = value Then Exit Property
+            If _InnerState = value Then Return
             Dim OldValue = _InnerState
             Dim OldValue = _InnerState
             _InnerState = value
             _InnerState = value
             '引发事件
             '引发事件
@@ -183,7 +179,7 @@ Public Class MyLoading
     Private IsLooping As Boolean = False
     Private IsLooping As Boolean = False
     Private Sub AniLoop()
     Private Sub AniLoop()
         '这坨循环代码也是老屎坑了,救救.jpg
         '这坨循环代码也是老屎坑了,救救.jpg
-        If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Exit Sub
+        If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Return
         IsLooping = True
         IsLooping = True
         ErrorAnimationWaiting = True
         ErrorAnimationWaiting = True
         AniStart({
         AniStart({
@@ -271,7 +267,7 @@ Public Class MyLoadingStateSimulator
             Return _LoadingState
             Return _LoadingState
         End Get
         End Get
         Set(value As MyLoadingState)
         Set(value As MyLoadingState)
-            If _LoadingState = value Then Exit Property
+            If _LoadingState = value Then Return
             Dim OldState = _LoadingState
             Dim OldState = _LoadingState
             _LoadingState = value
             _LoadingState = value
             RaiseEvent LoadingStateChanged(value, OldState)
             RaiseEvent LoadingStateChanged(value, OldState)

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

@@ -37,7 +37,7 @@
             Time = AnimationTimeOut
             Time = AnimationTimeOut
         End If
         End If
         '重复性验证
         '重复性验证
-        If ColorName = BackName Then Exit Sub
+        If ColorName = BackName Then Return
         ColorName = BackName
         ColorName = BackName
         '触发颜色动画
         '触发颜色动画
         If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
         If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画

+ 3 - 2
Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb

@@ -72,13 +72,14 @@
     End Sub
     End Sub
 
 
     Public Sub Btn1_Click() Handles Btn1.Click
     Public Sub Btn1_Click() Handles Btn1.Click
-        If MyConverter.IsExited OrElse Not TextArea.IsValidated Then Exit Sub
+        TextArea.Validate() '#5773
+        If MyConverter.IsExited OrElse Not TextArea.IsValidated Then Return
         MyConverter.IsExited = True
         MyConverter.IsExited = True
         MyConverter.Result = TextArea.Text
         MyConverter.Result = TextArea.Text
         Close()
         Close()
     End Sub
     End Sub
     Public Sub Btn2_Click() Handles Btn2.Click
     Public Sub Btn2_Click() Handles Btn2.Click
-        If MyConverter.IsExited Then Exit Sub
+        If MyConverter.IsExited Then Return
         MyConverter.IsExited = True
         MyConverter.IsExited = True
         MyConverter.Result = Nothing
         MyConverter.Result = Nothing
         Close()
         Close()

+ 2 - 2
Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb

@@ -77,13 +77,13 @@
     End Sub
     End Sub
 
 
     Public Sub Btn1_Click() Handles Btn1.Click
     Public Sub Btn1_Click() Handles Btn1.Click
-        If MyConverter.IsExited OrElse SelectedIndex = -1 Then Exit Sub
+        If MyConverter.IsExited OrElse SelectedIndex = -1 Then Return
         MyConverter.IsExited = True
         MyConverter.IsExited = True
         MyConverter.Result = SelectedIndex
         MyConverter.Result = SelectedIndex
         Close()
         Close()
     End Sub
     End Sub
     Public Sub Btn2_Click() Handles Btn2.Click
     Public Sub Btn2_Click() Handles Btn2.Click
-        If MyConverter.IsExited Then Exit Sub
+        If MyConverter.IsExited Then Return
         MyConverter.IsExited = True
         MyConverter.IsExited = True
         MyConverter.Result = Nothing
         MyConverter.Result = Nothing
         Close()
         Close()

+ 3 - 3
Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.vb

@@ -70,7 +70,7 @@
     End Sub
     End Sub
 
 
     Public Sub Btn1_Click() Handles Btn1.Click
     Public Sub Btn1_Click() Handles Btn1.Click
-        If MyConverter.IsExited Then Exit Sub
+        If MyConverter.IsExited Then Return
         If MyConverter.Button1Action IsNot Nothing Then
         If MyConverter.Button1Action IsNot Nothing Then
             MyConverter.Button1Action()
             MyConverter.Button1Action()
         Else
         Else
@@ -80,7 +80,7 @@
         End If
         End If
     End Sub
     End Sub
     Public Sub Btn2_Click() Handles Btn2.Click
     Public Sub Btn2_Click() Handles Btn2.Click
-        If MyConverter.IsExited Then Exit Sub
+        If MyConverter.IsExited Then Return
         If MyConverter.Button2Action IsNot Nothing Then
         If MyConverter.Button2Action IsNot Nothing Then
             MyConverter.Button2Action()
             MyConverter.Button2Action()
         Else
         Else
@@ -90,7 +90,7 @@
         End If
         End If
     End Sub
     End Sub
     Public Sub Btn3_Click() Handles Btn3.Click
     Public Sub Btn3_Click() Handles Btn3.Click
-        If MyConverter.IsExited Then Exit Sub
+        If MyConverter.IsExited Then Return
         If MyConverter.Button3Action IsNot Nothing Then
         If MyConverter.Button3Action IsNot Nothing Then
             MyConverter.Button3Action()
             MyConverter.Button3Action()
         Else
         Else

+ 16 - 19
Plain Craft Launcher 2/Controls/MyPageLeft.vb

@@ -3,15 +3,15 @@
     Private Uuid As Integer = GetUuid()
     Private Uuid As Integer = GetUuid()
 
 
     '执行逐个进入动画的控件
     '执行逐个进入动画的控件
-    Public Property AnimatedControl As FrameworkElement
+    Public Property AnimatedControl As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
         Get
         Get
             Return GetValue(AnimatedControlProperty)
             Return GetValue(AnimatedControlProperty)
         End Get
         End Get
-        Set(value As FrameworkElement)
+        Set(value As String)
             SetValue(AnimatedControlProperty, value)
             SetValue(AnimatedControlProperty, value)
         End Set
         End Set
     End Property
     End Property
-    Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(FrameworkElement), GetType(MyPageLeft), New PropertyMetadata(Nothing))
+    Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(String), GetType(MyPageLeft), New PropertyMetadata(Nothing))
 
 
     Public Sub TriggerShowAnimation()
     Public Sub TriggerShowAnimation()
         If AnimatedControl Is Nothing Then
         If AnimatedControl Is Nothing Then
@@ -39,16 +39,17 @@
                     Element.Opacity = 0
                     Element.Opacity = 0
                     Element.RenderTransform = New TranslateTransform(-25, 0)
                     Element.RenderTransform = New TranslateTransform(-25, 0)
                     If TypeOf Element Is MyListItem Then CType(Element, MyListItem).IsMouseOverAnimationEnabled = False
                     If TypeOf Element Is MyListItem Then CType(Element, MyListItem).IsMouseOverAnimationEnabled = False
-                    AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 200, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
+                    AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 100, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
                     AniList.Add(AaTranslateX(Element, 5, 200, Delay, New AniEaseOutFluent))
                     AniList.Add(AaTranslateX(Element, 5, 200, Delay, New AniEaseOutFluent))
                     AniList.Add(AaTranslateX(Element, 20, 300, Delay, New AniEaseOutBack(AniEasePower.Weak)))
                     AniList.Add(AaTranslateX(Element, 20, 300, Delay, New AniEaseOutBack(AniEasePower.Weak)))
                     If TypeOf Element Is MyListItem Then
                     If TypeOf Element Is MyListItem Then
-                        AniList.Add(AaCode(Sub()
-                                               CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
-                                               CType(Element, MyListItem).RefreshColor(Me, New EventArgs)
-                                           End Sub, Delay + 280))
+                        AniList.Add(AaCode(
+                        Sub()
+                            CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
+                            CType(Element, MyListItem).RefreshColor(Me, New EventArgs)
+                        End Sub, Delay + 280))
                     End If
                     End If
-                    Delay += Math.Max(8, 20 - Id) * 2.5
+                    Delay += Math.Max(15 - Id, 7) * 2
                     Id += 1
                     Id += 1
                 End If
                 End If
             Next
             Next
@@ -63,8 +64,8 @@
                 RenderTransformOrigin = New Point(0.5, 0.5)
                 RenderTransformOrigin = New Point(0.5, 0.5)
             End If
             End If
             AniStart({
             AniStart({
-                AaScaleTransform(Me, 0.95 - CType(RenderTransform, ScaleTransform).ScaleX, 130,, New AniEaseInFluent(AniEasePower.Weak)),
-                AaOpacity(Me, -Opacity, 100, 30)
+                AaScaleTransform(Me, 0.95 - CType(RenderTransform, ScaleTransform).ScaleX, 110,, New AniEaseInFluent(AniEasePower.Weak)),
+                AaOpacity(Me, -Opacity, 80, 30)
             }, "PageLeft PageChange " & Uuid)
             }, "PageLeft PageChange " & Uuid)
         Else
         Else
             '逐个退出动画
             '逐个退出动画
@@ -72,8 +73,8 @@
             Dim Id As Integer = 0
             Dim Id As Integer = 0
             Dim Controls = GetAllAnimControls()
             Dim Controls = GetAllAnimControls()
             For Each Element As FrameworkElement In Controls
             For Each Element As FrameworkElement In Controls
-                AniList.Add(AaOpacity(Element, -Element.Opacity, 60, 80 / Controls.Count * Id))
-                AniList.Add(AaTranslateX(Element, -6, 60, 80 / Controls.Count * Id))
+                AniList.Add(AaOpacity(Element, -Element.Opacity, 50, 70 / Controls.Count * Id))
+                AniList.Add(AaTranslateX(Element, -6, 50, 70 / Controls.Count * Id))
                 Id += 1
                 Id += 1
             Next
             Next
             AniStart(AniList, "PageLeft PageChange " & Uuid)
             AniStart(AniList, "PageLeft PageChange " & Uuid)
@@ -83,11 +84,11 @@
     '遍历获取所有需要生成动画的控件
     '遍历获取所有需要生成动画的控件
     Private Function GetAllAnimControls(Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
     Private Function GetAllAnimControls(Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
         Dim AllControls As New List(Of FrameworkElement)
         Dim AllControls As New List(Of FrameworkElement)
-        GetAllAnimControls(AnimatedControl, AllControls, IgnoreInvisibility)
+        GetAllAnimControls(FindName(AnimatedControl), AllControls, IgnoreInvisibility)
         Return AllControls
         Return AllControls
     End Function
     End Function
     Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
     Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
-        If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
+        If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return
         If TypeOf Element Is MyTextButton Then
         If TypeOf Element Is MyTextButton Then
             AllControls.Add(Element)
             AllControls.Add(Element)
         ElseIf TypeOf Element Is MyListItem Then
         ElseIf TypeOf Element Is MyListItem Then
@@ -104,7 +105,3 @@
     End Sub
     End Sub
 
 
 End Class
 End Class
-
-Public Interface IRefreshable
-    Sub Refresh()
-End Interface

+ 14 - 14
Plain Craft Launcher 2/Controls/MyPageRight.vb

@@ -3,16 +3,16 @@
     Public PageUuid As Integer = GetUuid()
     Public PageUuid As Integer = GetUuid()
 
 
     '“返回顶部” 按钮检测的滚动区域
     '“返回顶部” 按钮检测的滚动区域
-    Public Property PanScroll As MyScrollViewer
+    Public Property PanScroll As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
         Get
         Get
             Return GetValue(PanScrollProperty)
             Return GetValue(PanScrollProperty)
         End Get
         End Get
-        Set(value As MyScrollViewer)
+        Set(value As String)
             SetValue(PanScrollProperty, value)
             SetValue(PanScrollProperty, value)
         End Set
         End Set
     End Property
     End Property
     Private Shared ReadOnly PanScrollProperty =
     Private Shared ReadOnly PanScrollProperty =
-        DependencyProperty.Register("PanScroll", GetType(MyScrollViewer), GetType(MyPageRight), New PropertyMetadata(Nothing))
+        DependencyProperty.Register("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing))
 
 
     '当前状态
     '当前状态
     Public Enum PageStates
     Public Enum PageStates
@@ -33,7 +33,7 @@
             Return _PageState
             Return _PageState
         End Get
         End Get
         Set(value As PageStates)
         Set(value As PageStates)
-            If _PageState = value Then Exit Property
+            If _PageState = value Then Return
             _PageState = value
             _PageState = value
             If ModeDebug Then Log("[UI] 页面状态切换为 " & GetStringFromEnum(value))
             If ModeDebug Then Log("[UI] 页面状态切换为 " & GetStringFromEnum(value))
         End Set
         End Set
@@ -106,7 +106,7 @@
     End Sub
     End Sub
     '重试
     '重试
     Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试
     Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试
-        If Not PageLoaderAutoRun Then Exit Sub
+        If Not PageLoaderAutoRun Then Return
         If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
         If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
             PageLoader.Start(CType(PageLoader, Object).StartGetInput(Input, PageLoaderInputInvoke), IsForceRestart:=IsForceRestart)
             PageLoader.Start(CType(PageLoader, Object).StartGetInput(Input, PageLoaderInputInvoke), IsForceRestart:=IsForceRestart)
         Else
         Else
@@ -187,7 +187,7 @@
     ''' 需要立即切换至 Empty。
     ''' 需要立即切换至 Empty。
     ''' </summary>
     ''' </summary>
     Public Sub PageOnForceExit()
     Public Sub PageOnForceExit()
-        If PageState = PageStates.Empty Then Exit Sub
+        If PageState = PageStates.Empty Then Return
         If ModeDebug Then Log("[UI] 已触发 PageOnForceExit")
         If ModeDebug Then Log("[UI] 已触发 PageOnForceExit")
         PageState = PageStates.Empty
         PageState = PageStates.Empty
         AniStop("PageRight PageChange " & PageUuid)
         AniStop("PageRight PageChange " & PageUuid)
@@ -302,7 +302,7 @@
     Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState)
     Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState)
         Select Case NewState
         Select Case NewState
             Case LoadState.Failed, LoadState.Loading
             Case LoadState.Failed, LoadState.Loading
-                If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Exit Sub
+                If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Return
                 If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Start/Refresh)")
                 If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Start/Refresh)")
                 '(重新)开始运行
                 '(重新)开始运行
                 '需要从部分状态切换到 ReloadExit
                 '需要从部分状态切换到 ReloadExit
@@ -314,7 +314,7 @@
                         PageState = PageStates.ContentExit
                         PageState = PageStates.ContentExit
                 End Select
                 End Select
             Case LoadState.Finished, LoadState.Aborted, LoadState.Waiting
             Case LoadState.Finished, LoadState.Aborted, LoadState.Waiting
-                If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Exit Sub
+                If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Return
                 If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop/Abort)")
                 If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop/Abort)")
                 '运行结束
                 '运行结束
                 '需要从 LoaderWait 切换到 ContentEnter,或从 LoaderStay 切换到 LoaderExit
                 '需要从 LoaderWait 切换到 ContentEnter,或从 LoaderStay 切换到 LoaderExit
@@ -358,10 +358,10 @@
                 Else
                 Else
                     Control.Opacity = 0
                     Control.Opacity = 0
                     Control.RenderTransform = New TranslateTransform(0, -16)
                     Control.RenderTransform = New TranslateTransform(0, -16)
-                    AniList.Add(AaOpacity(Control, 1, 150, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
+                    AniList.Add(AaOpacity(Control, 1, 100, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
                     AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent))
                     AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent))
                     AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack))
                     AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack))
-                    Delay += 40
+                    Delay += 25
                 End If
                 End If
             Next
             Next
         Next
         Next
@@ -387,9 +387,9 @@
                     CType(Control, MyExtraTextButton).Show = False
                     CType(Control, MyExtraTextButton).Show = False
                 Else
                 Else
                     Control.IsHitTestVisible = False
                     Control.IsHitTestVisible = False
-                    AniList.Add(AaOpacity(Control, -1, 90, Delay))
-                    AniList.Add(AaTranslateY(Control, -6, 90, Delay))
-                    Delay += 20
+                    AniList.Add(AaOpacity(Control, -1, 70, Delay))
+                    AniList.Add(AaTranslateY(Control, -6, 70, Delay))
+                    Delay += 15
                 End If
                 End If
             Next
             Next
         Next
         Next
@@ -423,7 +423,7 @@
         Return AllControls.Except(DisabledPageAnimControls)
         Return AllControls.Except(DisabledPageAnimControls)
     End Function
     End Function
     Private Sub _GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
     Private Sub _GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
-        If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
+        If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return
         If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is MyExtraTextButton OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
         If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is MyExtraTextButton OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
             AllControls.Add(Element)
             AllControls.Add(Element)
         ElseIf TypeOf Element Is ContentControl Then
         ElseIf TypeOf Element Is ContentControl Then

+ 5 - 5
Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb

@@ -41,7 +41,7 @@ Public Class MyRadioBox
                 RaiseEvent PreviewCheck(Me, e)
                 RaiseEvent PreviewCheck(Me, e)
                 If e.Handled Then
                 If e.Handled Then
                     Radiobox_MouseLeave()
                     Radiobox_MouseLeave()
-                    Exit Sub
+                    Return
                 End If
                 End If
             End If
             End If
 
 
@@ -54,7 +54,7 @@ Public Class MyRadioBox
             End If
             End If
 
 
             '保证只有一个单选框选中
             '保证只有一个单选框选中
-            If Parent Is Nothing Then Exit Sub
+            If Parent Is Nothing Then Return
             Dim RadioboxList As New List(Of MyRadioBox)
             Dim RadioboxList As New List(Of MyRadioBox)
             Dim CheckedCount As Integer = 0
             Dim CheckedCount As Integer = 0
             For Each Control In CType(Parent, Object).Children '收集控件列表与选中个数
             For Each Control In CType(Parent, Object).Children '收集控件列表与选中个数
@@ -172,7 +172,7 @@ Public Class MyRadioBox
     Private MouseDowned As Boolean = False
     Private MouseDowned As Boolean = False
     Private AllowMouseDown As Boolean = True
     Private AllowMouseDown As Boolean = True
     Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
     Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
-        If Not MouseDowned Then Exit Sub
+        If Not MouseDowned Then Return
         Log("[Control] 按下单选框:" & Text)
         Log("[Control] 按下单选框:" & Text)
         SetChecked(True, True)
         SetChecked(True, True)
         MouseDowned = False
         MouseDowned = False
@@ -187,7 +187,7 @@ Public Class MyRadioBox
         End If
         End If
     End Sub
     End Sub
     Private Sub Radiobox_MouseLeave() Handles Me.MouseLeave
     Private Sub Radiobox_MouseLeave() Handles Me.MouseLeave
-        If Not MouseDowned Then Exit Sub
+        If Not MouseDowned Then Return
         MouseDowned = False
         MouseDowned = False
         AniStart(AaColor(ShapeBorder, Ellipse.FillProperty, "ColorBrushHalfWhite", 100), "MyRadioBox Background " & Uuid)
         AniStart(AaColor(ShapeBorder, Ellipse.FillProperty, "ColorBrushHalfWhite", 100), "MyRadioBox Background " & Uuid)
         If Not Checked Then
         If Not Checked Then
@@ -224,7 +224,7 @@ Public Class MyRadioBox
         AniStart(AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn), "MyRadioBox TextColor " & Uuid)
         AniStart(AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn), "MyRadioBox TextColor " & Uuid)
     End Sub
     End Sub
     Private Sub Radiobox_MouseLeaveAnimation() Handles Me.MouseLeave
     Private Sub Radiobox_MouseLeaveAnimation() Handles Me.MouseLeave
-        If Not Me.IsEnabled Then Exit Sub 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
+        If Not Me.IsEnabled Then Return 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
         If IsLoaded AndAlso AniControlEnabled = 0 Then
         If IsLoaded AndAlso AniControlEnabled = 0 Then
             AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
             AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
             AniStart(AaColor(LabText, TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
             AniStart(AaColor(LabText, TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)

+ 6 - 6
Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb

@@ -62,7 +62,7 @@ Public Class MyRadioButton
 
 
             '保证只有一个单选框选中
             '保证只有一个单选框选中
 
 
-            If IsNothing(Parent) Then Exit Sub
+            If IsNothing(Parent) Then Return
             Dim RadioboxList As New List(Of MyRadioButton)
             Dim RadioboxList As New List(Of MyRadioButton)
             Dim CheckedCount As Integer = 0
             Dim CheckedCount As Integer = 0
             '收集控件列表与选中个数
             '收集控件列表与选中个数
@@ -101,7 +101,7 @@ Public Class MyRadioButton
 
 
             '更改动画
             '更改动画
 
 
-            If Not IsChanged Then Exit Sub
+            If Not IsChanged Then Return
             RefreshColor(Nothing, anime)
             RefreshColor(Nothing, anime)
 
 
             '触发事件
             '触发事件
@@ -147,17 +147,17 @@ Public Class MyRadioButton
     Public Event PreviewClick(sender As Object, e As RouteEventArgs)
     Public Event PreviewClick(sender As Object, e As RouteEventArgs)
     Private IsMouseDown As Boolean = False
     Private IsMouseDown As Boolean = False
     Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
     Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
-        If Checked Then Exit Sub
-        If Not IsMouseDown Then Exit Sub
+        If Checked Then Return
+        If Not IsMouseDown Then Return
         Log("[Control] 按下单选按钮:" & Text)
         Log("[Control] 按下单选按钮:" & Text)
         IsMouseDown = False
         IsMouseDown = False
         Dim e As New RouteEventArgs(True)
         Dim e As New RouteEventArgs(True)
         RaiseEvent PreviewClick(Me, e)
         RaiseEvent PreviewClick(Me, e)
-        If e.Handled Then Exit Sub
+        If e.Handled Then Return
         SetChecked(True, True, True)
         SetChecked(True, True, True)
     End Sub
     End Sub
     Private Sub Radiobox_MouseDown() Handles Me.MouseLeftButtonDown
     Private Sub Radiobox_MouseDown() Handles Me.MouseLeftButtonDown
-        If Checked Then Exit Sub
+        If Checked Then Return
         IsMouseDown = True
         IsMouseDown = True
         RefreshColor()
         RefreshColor()
     End Sub
     End Sub

+ 2 - 2
Plain Craft Launcher 2/Controls/MyResizer.vb

@@ -301,7 +301,7 @@ Public Class MyResizer
     End Sub
     End Sub
 
 
     Private Sub updateMouseDown()
     Private Sub updateMouseDown()
-        Dim flag As Boolean = Mouse.LeftButton = MouseButtonState.Released
+        Dim flag = (GetAsyncKeyState(&H1) And &H8000) = 0 '调用原生API判断鼠标是否抬起,如果使用WPF的API的话鼠标不在窗口上时不会更新状态 (#5655)
         If flag Then
         If flag Then
             resizeRight = False
             resizeRight = False
             resizeLeft = False
             resizeLeft = False
@@ -311,5 +311,5 @@ Public Class MyResizer
     End Sub
     End Sub
 
 
     Private Declare Function GetCursorPos Lib "user32.dll" (<Out()> ByRef lpPoint As PointAPI) As Boolean
     Private Declare Function GetCursorPos Lib "user32.dll" (<Out()> ByRef lpPoint As PointAPI) As Boolean
-
+    Private Declare Function GetAsyncKeyState Lib "user32.dll" (vKey As Integer) As Short
 End Class
 End Class

+ 3 - 4
Plain Craft Launcher 2/Controls/MyScrollViewer.vb

@@ -6,16 +6,15 @@
 
 
     Private RealOffset As Double
     Private RealOffset As Double
     Private Sub MyScrollViewer_PreviewMouseWheel(sender As Object, e As MouseWheelEventArgs) Handles Me.PreviewMouseWheel
     Private Sub MyScrollViewer_PreviewMouseWheel(sender As Object, e As MouseWheelEventArgs) Handles Me.PreviewMouseWheel
-        If e.Delta = 0 OrElse ActualHeight = 0 OrElse ScrollableHeight = 0 Then Exit Sub
+        If e.Delta = 0 OrElse ActualHeight = 0 OrElse ScrollableHeight = 0 Then Return
         Dim SourceType = e.Source.GetType
         Dim SourceType = e.Source.GetType
         If Content.TemplatedParent Is Nothing AndAlso (
         If Content.TemplatedParent Is Nothing AndAlso (
                 (GetType(ComboBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, ComboBox).IsDropDownOpen) OrElse
                 (GetType(ComboBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, ComboBox).IsDropDownOpen) OrElse
                 (GetType(TextBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, TextBox).AcceptsReturn) OrElse
                 (GetType(TextBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, TextBox).AcceptsReturn) OrElse
                 GetType(ComboBoxItem).IsAssignableFrom(SourceType) OrElse
                 GetType(ComboBoxItem).IsAssignableFrom(SourceType) OrElse
-                TypeOf e.Source Is CheckBox
-           ) Then
+                TypeOf e.Source Is CheckBox) Then
             '如果当前是在对有滚动条的下拉框或文本框执行,则不接管操作
             '如果当前是在对有滚动条的下拉框或文本框执行,则不接管操作
-            Exit Sub
+            Return
         End If
         End If
         e.Handled = True
         e.Handled = True
         PerformVerticalOffsetDelta(-e.Delta)
         PerformVerticalOffsetDelta(-e.Delta)

+ 7 - 7
Plain Craft Launcher 2/Controls/MySlider.xaml.vb

@@ -14,7 +14,7 @@
             Return _MaxValue
             Return _MaxValue
         End Get
         End Get
         Set(value As Integer)
         Set(value As Integer)
-            If value = _MaxValue Then Exit Property
+            If value = _MaxValue Then Return
             _MaxValue = value
             _MaxValue = value
             RefreshWidth(Nothing, Nothing)
             RefreshWidth(Nothing, Nothing)
         End Set
         End Set
@@ -29,7 +29,7 @@
             Try
             Try
 
 
                 newValue = MathClamp(newValue, 0, MaxValue)
                 newValue = MathClamp(newValue, 0, MaxValue)
-                If _Value = newValue Then Exit Property
+                If _Value = newValue Then Return
 
 
                 '触发 Preview 事件,修改新值
                 '触发 Preview 事件,修改新值
                 Dim OldValue = _Value
                 Dim OldValue = _Value
@@ -40,12 +40,12 @@
                     If e.Handled Then
                     If e.Handled Then
                         _Value = OldValue
                         _Value = OldValue
                         DragStop()
                         DragStop()
-                        Exit Property
+                        Return
                     End If
                     End If
                 End If
                 End If
 
 
                 If IsLoaded AndAlso AniControlEnabled = 0 Then
                 If IsLoaded AndAlso AniControlEnabled = 0 Then
-                    If ActualWidth < ShapeDot.Width Then Exit Property
+                    If ActualWidth < ShapeDot.Width Then Return
                     Dim NewWidth As Double = _Value / MaxValue * (ActualWidth - ShapeDot.Width)
                     Dim NewWidth As Double = _Value / MaxValue * (ActualWidth - ShapeDot.Width)
                     Dim DeltaProcess As Double = Math.Abs(LineFore.Width / (ActualWidth - ShapeDot.Width) - _Value / MaxValue)
                     Dim DeltaProcess As Double = Math.Abs(LineFore.Width / (ActualWidth - ShapeDot.Width) - _Value / MaxValue)
                     Dim Time As Double = (1 - Math.Pow(1 - DeltaProcess, 3)) * 300 + If(ChangeByKey, 100, 0)
                     Dim Time As Double = (1 - Math.Pow(1 - DeltaProcess, 3)) * 300 + If(ChangeByKey, 100, 0)
@@ -103,7 +103,7 @@
         Popup.IsOpen = False
         Popup.IsOpen = False
     End Sub
     End Sub
     Public Sub RefreshPopup()
     Public Sub RefreshPopup()
-        If GetHintText Is Nothing Then Exit Sub
+        If GetHintText Is Nothing Then Return
         Popup.IsOpen = True
         Popup.IsOpen = True
         TextHint.Text = GetHintText.DynamicInvoke(Value)
         TextHint.Text = GetHintText.DynamicInvoke(Value)
         Dim typeface As New Typeface(TextHint.FontFamily, TextHint.FontStyle, TextHint.FontWeight, TextHint.FontStretch)
         Dim typeface As New Typeface(TextHint.FontFamily, TextHint.FontStyle, TextHint.FontWeight, TextHint.FontStretch)
@@ -165,7 +165,7 @@
     End Sub
     End Sub
     Private Sub MySlider_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
     Private Sub MySlider_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
         '拒绝一边拖动一边用按键改变
         '拒绝一边拖动一边用按键改变
-        If ReferenceEquals(Me, DragControl) Then Exit Sub
+        If ReferenceEquals(Me, DragControl) Then Return
         '改变值
         '改变值
         If e.Key = Key.Left Then
         If e.Key = Key.Left Then
             ChangeByKey = True
             ChangeByKey = True
@@ -178,7 +178,7 @@
             ChangeByKey = False
             ChangeByKey = False
             e.Handled = True
             e.Handled = True
         Else
         Else
-            Exit Sub
+            Return
         End If
         End If
         '更新 Popup
         '更新 Popup
         If GetHintText IsNot Nothing Then
         If GetHintText IsNot Nothing Then

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

@@ -36,7 +36,7 @@
     End Property
     End Property
     Public Overrides Sub OnApplyTemplate()
     Public Overrides Sub OnApplyTemplate()
         MyBase.OnApplyTemplate()
         MyBase.OnApplyTemplate()
-        If HintText = "" OrElse labHint.Text <> "" Then Exit Sub
+        If HintText = "" OrElse labHint.Text <> "" Then Return
         labHint.Text = If(Text = "", HintText, "")
         labHint.Text = If(Text = "", HintText, "")
     End Sub
     End Sub
 
 
@@ -217,11 +217,11 @@
             IsTextChanged = IsLoaded
             IsTextChanged = IsLoaded
             '进行输入验证
             '进行输入验证
             Validate()
             Validate()
-            If Not IsValidated Then Exit Sub
+            If Not IsValidated Then Return
             '改变文本
             '改变文本
             RaiseEvent ValidatedTextChanged(sender, e)
             RaiseEvent ValidatedTextChanged(sender, e)
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "进行输入验证时出错", LogLevel.Assert)
+            Log(ex, "进行输入验证时出错", LogLevel.Critical)
         End Try
         End Try
     End Sub
     End Sub
 
 
@@ -230,7 +230,7 @@
     Private Sub RefreshColor() Handles Me.IsEnabledChanged, Me.MouseEnter, Me.MouseLeave, Me.GotFocus, Me.LostFocus
     Private Sub RefreshColor() Handles Me.IsEnabledChanged, Me.MouseEnter, Me.MouseLeave, Me.GotFocus, Me.LostFocus
         Try
         Try
             '不对 ComboBox 从属进行动画
             '不对 ComboBox 从属进行动画
-            If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Exit Sub
+            If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Return
             '判断当前颜色
             '判断当前颜色
             Dim ForeColorName As String, BackColorName As String
             Dim ForeColorName As String, BackColorName As String
             Dim AnimationTime As Integer
             Dim AnimationTime As Integer
@@ -264,7 +264,7 @@
             If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
             If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
                 '有动画
                 '有动画
                 AniStart({
                 AniStart({
-                         AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
+                         AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
                          AaColor(Me, BackgroundProperty, BackColorName, AnimationTime)
                          AaColor(Me, BackgroundProperty, BackColorName, AnimationTime)
                      }, "MyTextBox Color " & Uuid)
                      }, "MyTextBox Color " & Uuid)
             Else
             Else
@@ -280,7 +280,7 @@
     End Sub
     End Sub
     Private Sub RefreshTextColor() Handles Me.IsEnabledChanged
     Private Sub RefreshTextColor() Handles Me.IsEnabledChanged
         Dim NewColor As MyColor = If(IsEnabled, ColorGray1, ColorGray4)
         Dim NewColor As MyColor = If(IsEnabled, ColorGray1, ColorGray4)
-        If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Exit Sub
+        If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Return
         If IsLoaded AndAlso AniControlEnabled = 0 AndAlso Not Text = "" Then
         If IsLoaded AndAlso AniControlEnabled = 0 AndAlso Not Text = "" Then
             '有动画
             '有动画
             AniStart({AaColor(Me, ForegroundProperty, If(IsEnabled, "ColorBrushGray1", "ColorBrushGray4"), 200)}, "MyTextBox TextColor " & Uuid)
             AniStart({AaColor(Me, ForegroundProperty, If(IsEnabled, "ColorBrushGray1", "ColorBrushGray4"), 200)}, "MyTextBox TextColor " & Uuid)

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

@@ -57,7 +57,7 @@
     Private Const AnimationTimeIn As Integer = 100
     Private Const AnimationTimeIn As Integer = 100
     Private Const AnimationTimeOut As Integer = 200
     Private Const AnimationTimeOut As Integer = 200
     Private ColorName As String
     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 ForeName As String
         Dim Time As Integer
         Dim Time As Integer
@@ -72,7 +72,7 @@
             Time = AnimationTimeOut
             Time = AnimationTimeOut
         End If
         End If
         '重复性验证
         '重复性验证
-        If ColorName = ForeName Then Exit Sub
+        If ColorName = ForeName Then Return
         ColorName = ForeName
         ColorName = ForeName
         '触发颜色动画
         '触发颜色动画
         If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
         If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画

+ 8 - 10
Plain Craft Launcher 2/FormMain.xaml

@@ -99,9 +99,7 @@
                 <Grid x:Name="PanTitle" Height="48">
                 <Grid x:Name="PanTitle" Height="48">
                     <Grid.Background>
                     <Grid.Background>
                         <LinearGradientBrush EndPoint="1,0" StartPoint="0,0">
                         <LinearGradientBrush EndPoint="1,0" StartPoint="0,0">
-                            <GradientStop Color="{StaticResource ColorObject3}" Offset="0"/>
-                            <GradientStop Color="{StaticResource ColorObject2}" Offset="0.5"/>
-                            <GradientStop Color="{StaticResource ColorObject3}" Offset="1"/>
+                            <GradientStop Color="{StaticResource ColorObject4}" Offset="0"/>
                         </LinearGradientBrush>
                         </LinearGradientBrush>
                     </Grid.Background>
                     </Grid.Background>
                     <local:MyImage x:Name="ImgTitle" IsHitTestVisible="False" Stretch="UniformToFill" IsEnabled="False" RenderOptions.BitmapScalingMode="LowQuality" />
                     <local:MyImage x:Name="ImgTitle" IsHitTestVisible="False" Stretch="UniformToFill" IsEnabled="False" RenderOptions.BitmapScalingMode="LowQuality" />
@@ -115,19 +113,19 @@
                             <ColumnDefinition Width="Auto" />
                             <ColumnDefinition Width="Auto" />
                             <ColumnDefinition Width="1*" />
                             <ColumnDefinition Width="1*" />
                         </Grid.ColumnDefinitions>
                         </Grid.ColumnDefinitions>
-                        <Path x:Name="ShapeTitleLogo" Data="M26,29 v-25 h5 a7,7 180 0 1 0,14 h-5 M61,6.5 a10,11.5 180 1 0 0,18 M68,2.5 v24.5 h12 M98,2 v27 M107,2 v27" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="19,15.5,0,15.5" Stretch="fill" Stroke="White" StrokeThickness="1.9" Width="53" />
+                        <Path x:Name="ShapeTitleLogo" Data="M26,29 v-25 h6 a7,7 180 0 1 0,14 h-6 M62.5,6.5 a10,11.5 180 1 0 0,18 M71,2.5 v24.5 h13.5" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="19,15.5,0,15.5" Stretch="fill" Stroke="White" StrokeThickness="2.2" Width="39" />
                         <TextBlock x:Name="LabTitleLogo" Visibility="Collapsed" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="18,0,0,0" Foreground="White" FontSize="17" VerticalAlignment="Center" />
                         <TextBlock x:Name="LabTitleLogo" Visibility="Collapsed" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="18,0,0,0" Foreground="White" FontSize="17" VerticalAlignment="Center" />
                         <local:MyImage x:Name="ImageTitleLogo" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="7" VerticalAlignment="Center" Visibility="Collapsed" />
                         <local:MyImage x:Name="ImageTitleLogo" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="7" VerticalAlignment="Center" Visibility="Collapsed" />
                         <StackPanel Grid.Column="1" x:Name="PanTitleSelect" HorizontalAlignment="Left" Height="27" VerticalAlignment="Center" Orientation="Horizontal" Margin="13,0">
                         <StackPanel Grid.Column="1" x:Name="PanTitleSelect" HorizontalAlignment="Left" Height="27" VerticalAlignment="Center" Orientation="Horizontal" Margin="13,0">
-                            <local:MyRadioButton x:Name="BtnTitleSelect0" Text="{DynamicResource LangTitleHome}" Tag="0" Margin="5,0" Checked="True" Padding="2,0"
-                                             LogoScale="0.95" Logo="M985.403 552.96C985.403 812.702 773.041 1024 512 1024S38.597 812.702 38.597 552.96c0-158.208 79.163-304.955 211.771-392.625 31.074-20.52 72.94-11.973 93.46 19.101s12.012 72.94-19.062 93.46c-94.681 62.622-151.237 167.306-151.237 280.064 0 185.344 151.788 336.108 338.471 336.108S850.471 738.304 850.471 552.96c0-114.649-57.974-220.278-155.096-282.585-31.35-20.125-40.448-61.833-20.362-93.184s61.794-40.448 93.184-20.4c135.995 87.236 217.206 235.362 217.206 396.169zM512 560.128c37.258 0 67.466-30.208 67.466-67.466V67.466C579.466 30.208 549.258 0 512 0s-67.466 30.208-67.466 67.466v425.196c0 37.258 30.208 67.466 67.466 67.466z" />
-                            <local:MyRadioButton x:Name="BtnTitleSelect1" Text="{DynamicResource LangTitleDownload}" Tag="1" Margin="5,0" Padding="2,0"
+                            <local:MyRadioButton x:Name="BtnTitleSelect0" Text="启动" Tag="0" Margin="5,0" Checked="True" Padding="2,0"
+                                             LogoScale="0.9" Logo="M52.1,164.5c-1.4,0-3.1-0.5-4.2-1.3c-2.6-1.7-4-4.2-4-7V43.8c0-2.9,1.6-5.8,4.1-7c1.2-0.8,2.7-1.2,4.1-1.2c1.5,0,2.9,0.4,4.2,1.2L153.1,93c0,0,0.1,0,0.1,0.1c2.6,1.7,4,4.2,4,7c0,3-1.7,5.8-4.2,7.1l-96.8,56.2C55.1,164,53.5,164.5,52.1,164.5z M60.4,142.1l72.1-42.1L60.4,58.2V142.1z" />
+                            <local:MyRadioButton x:Name="BtnTitleSelect1" Text="下载" Tag="1" Margin="5,0" Padding="2,0"
                                              LogoScale="0.9" Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" />
                                              LogoScale="0.9" Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" />
                             <local:MyRadioButton x:Name="BtnTitleSelect2" Text="联机" Tag="2" Margin="5,0" Padding="2,0"
                             <local:MyRadioButton x:Name="BtnTitleSelect2" Text="联机" Tag="2" Margin="5,0" Padding="2,0"
                                              LogoScale="1.05" Logo="M512 817c-48.601 0-88-39.399-88-88s39.399-88 88-88 88 39.399 88 88-39.399 88-88 88zM237.671 565.74C308.335 474.58 397.369 429 504.774 429c118.433 0 214.225 55.421 287.377 166.264l-53.407 30.369c-13.84 7.87-31.362 4.367-41.114-8.219-50.291-64.911-114.577-97.367-192.856-97.367-86.851 0-156.835 40.318-209.95 120.953l-47.995-28.02c-15.263-8.91-20.412-28.507-11.502-43.77a32 32 0 0 1 2.344-3.47zM107.691 419.47C205.24 278.491 337.805 208 505.379 208c178.77 0 317.694 80.224 416.772 240.672l-56.54 31.73c-13.686 7.68-30.922 4.303-40.697-7.975C735.581 360.213 629.07 303.009 505.38 300.815 373.997 298.485 261.637 362.88 168.3 494l-50.116-28.505c-15.362-8.738-20.732-28.275-11.994-43.637a32 32 0 0 1 1.5-2.387z"/>
                                              LogoScale="1.05" Logo="M512 817c-48.601 0-88-39.399-88-88s39.399-88 88-88 88 39.399 88 88-39.399 88-88 88zM237.671 565.74C308.335 474.58 397.369 429 504.774 429c118.433 0 214.225 55.421 287.377 166.264l-53.407 30.369c-13.84 7.87-31.362 4.367-41.114-8.219-50.291-64.911-114.577-97.367-192.856-97.367-86.851 0-156.835 40.318-209.95 120.953l-47.995-28.02c-15.263-8.91-20.412-28.507-11.502-43.77a32 32 0 0 1 2.344-3.47zM107.691 419.47C205.24 278.491 337.805 208 505.379 208c178.77 0 317.694 80.224 416.772 240.672l-56.54 31.73c-13.686 7.68-30.922 4.303-40.697-7.975C735.581 360.213 629.07 303.009 505.38 300.815 373.997 298.485 261.637 362.88 168.3 494l-50.116-28.505c-15.362-8.738-20.732-28.275-11.994-43.637a32 32 0 0 1 1.5-2.387z"/>
-                            <local:MyRadioButton x:Name="BtnTitleSelect3" Text="{DynamicResource LangTitleSetup}" Tag="3" Margin="5,0" Padding="2,0"
+                            <local:MyRadioButton x:Name="BtnTitleSelect3" Text="设置" Tag="3" Margin="5,0" Padding="2,0"
                                              LogoScale="1.1" Logo="M940.4 463.7L773.3 174.2c-17.3-30-49.2-48.4-83.8-48.4H340.2c-34.6 0-66.5 18.5-83.8 48.4L89.2 463.7c-17.3 30-17.3 66.9 0 96.8L256.4 850c17.3 30 49.2 48.4 83.8 48.4h349.2c34.6 0 66.5-18.5 83.8-48.4l167.2-289.5c17.3-29.9 17.3-66.8 0-96.8z m-94.6 96.8L725.9 768.1c-17.3 30-49.2 48.4-83.8 48.4H387.5c-34.6 0-66.5-18.5-83.8-48.4L183.9 560.5c-17.3-30-17.3-66.9 0-96.8l119.8-207.5c17.3-30 49.2-48.4 83.8-48.4h254.6c34.6 0 66.5 18.5 83.8 48.4l119.8 207.5c17.3 30 17.3 66.9 0.1 96.8z M522.3 321.2c-2.5-0.1-5-0.2-7.5-0.2-119.9 0-214 110.3-186.3 235 15.8 70.9 71.5 126.6 142.4 142.4 17.5 3.9 34.7 5.4 51.4 4.7 102.1-3.9 183.6-87.9 183.6-191 0.1-103-81.5-187-183.6-190.9z m68.6 269.1c-18.5 18-43 28.9-68.6 30.7l-6 0.3c-30.2 0.4-58.6-11.4-79.7-33-19.5-20.1-30.7-47-30.9-75-0.3-29.6 11.1-57.4 32-78.3 20.6-20.6 48-32 77.2-32 2.5 0 5 0.1 7.5 0.3 26.7 1.8 51.5 13.2 70.5 32.5 19.6 20 30.8 46.9 31.2 74.9 0.2 30.2-11.5 58.6-33.2 79.6z"/>
                                              LogoScale="1.1" Logo="M940.4 463.7L773.3 174.2c-17.3-30-49.2-48.4-83.8-48.4H340.2c-34.6 0-66.5 18.5-83.8 48.4L89.2 463.7c-17.3 30-17.3 66.9 0 96.8L256.4 850c17.3 30 49.2 48.4 83.8 48.4h349.2c34.6 0 66.5-18.5 83.8-48.4l167.2-289.5c17.3-29.9 17.3-66.8 0-96.8z m-94.6 96.8L725.9 768.1c-17.3 30-49.2 48.4-83.8 48.4H387.5c-34.6 0-66.5-18.5-83.8-48.4L183.9 560.5c-17.3-30-17.3-66.9 0-96.8l119.8-207.5c17.3-30 49.2-48.4 83.8-48.4h254.6c34.6 0 66.5 18.5 83.8 48.4l119.8 207.5c17.3 30 17.3 66.9 0.1 96.8z M522.3 321.2c-2.5-0.1-5-0.2-7.5-0.2-119.9 0-214 110.3-186.3 235 15.8 70.9 71.5 126.6 142.4 142.4 17.5 3.9 34.7 5.4 51.4 4.7 102.1-3.9 183.6-87.9 183.6-191 0.1-103-81.5-187-183.6-190.9z m68.6 269.1c-18.5 18-43 28.9-68.6 30.7l-6 0.3c-30.2 0.4-58.6-11.4-79.7-33-19.5-20.1-30.7-47-30.9-75-0.3-29.6 11.1-57.4 32-78.3 20.6-20.6 48-32 77.2-32 2.5 0 5 0.1 7.5 0.3 26.7 1.8 51.5 13.2 70.5 32.5 19.6 20 30.8 46.9 31.2 74.9 0.2 30.2-11.5 58.6-33.2 79.6z"/>
-                            <local:MyRadioButton x:Name="BtnTitleSelect4" Text="{DynamicResource LangTitleOther}" Tag="4" Margin="5,0" Padding="2,0"
+                            <local:MyRadioButton x:Name="BtnTitleSelect4" Text="更多" Tag="4" Margin="5,0" Padding="2,0"
                                              LogoScale="0.93" Logo="M364 0h-273C40 0 0 40 0 91v273C0 414 40 455 91 455h273C414 455 455 414 455 364V91C455 40 414 0 364 0zM341 341H113V113h227v227zM933 0h-273C609 0 568 40 568 91v273c0 50 40 91 91 91h273C983 455 1024 414 1024 364V91c0-50-40-91-90-91zM910 341h-227V113h227v227zM364 568h-273C40 568 0 609 0 659v273c0 50 40 91 91 91h273C414 1024 455 983 455 932v-273C455 609 414 568 364 568zM341 910H113v-227h227v227zM933 568h-273c-50 0-91 40-91 91v273c0 50 40 91 91 91h273c50 0 90-40 90-91v-273c0-50-40-90-90-90zM910 910h-227v-227h227v227z"/>
                                              LogoScale="0.93" Logo="M364 0h-273C40 0 0 40 0 91v273C0 414 40 455 91 455h273C414 455 455 414 455 364V91C455 40 414 0 364 0zM341 341H113V113h227v227zM933 0h-273C609 0 568 40 568 91v273c0 50 40 91 91 91h273C983 455 1024 414 1024 364V91c0-50-40-91-90-91zM910 341h-227V113h227v227zM364 568h-273C40 568 0 609 0 659v273c0 50 40 91 91 91h273C414 1024 455 983 455 932v-273C455 609 414 568 364 568zM341 910H113v-227h227v227zM933 568h-273c-50 0-91 40-91 91v273c0 50 40 91 91 91h273c50 0 90-40 90-91v-273c0-50-40-90-90-90zM910 910h-227v-227h227v227z"/>
                         </StackPanel>
                         </StackPanel>
                     </Grid>
                     </Grid>
@@ -145,7 +143,7 @@
                     <Rectangle x:Name="RectLeftBackground" Width="0" Fill="{DynamicResource ColorBrushBackgroundTransparentSidebar}" IsHitTestVisible="False" />
                     <Rectangle x:Name="RectLeftBackground" Width="0" Fill="{DynamicResource ColorBrushBackgroundTransparentSidebar}" IsHitTestVisible="False" />
                     <Rectangle x:Name="RectLeftShadow" Width="4" HorizontalAlignment="Left" IsHitTestVisible="False" Grid.Column="1" >
                     <Rectangle x:Name="RectLeftShadow" Width="4" HorizontalAlignment="Left" IsHitTestVisible="False" Grid.Column="1" >
                         <Rectangle.Fill>
                         <Rectangle.Fill>
-                            <LinearGradientBrush EndPoint="1,0" StartPoint="0,0" Opacity="0.085">
+                            <LinearGradientBrush EndPoint="1,0" StartPoint="0,0" Opacity="0.04">
                                 <GradientStop Color="#000000" Offset="0"/>
                                 <GradientStop Color="#000000" Offset="0"/>
                                 <GradientStop Color="#00000000" Offset="1"/>
                                 <GradientStop Color="#00000000" Offset="1"/>
                             </LinearGradientBrush>
                             </LinearGradientBrush>

+ 222 - 105
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -1,4 +1,5 @@
 Imports System.ComponentModel
 Imports System.ComponentModel
+Imports System.Windows.Interop
 
 
 Public Class FormMain
 Public Class FormMain
 
 
@@ -10,7 +11,44 @@ Public Class FormMain
         Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
         Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
         '统计更新日志条目
         '统计更新日志条目
 #If BETA Then
 #If BETA Then
-        If LastVersion < 354 Then 'Release 2.9.3
+        If LastVersion < 369 Then 'Release 2.10.8
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "新增:允许在版本设置中设置禁止更新 Mod,以防整合包玩家误操作"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:若整合包需要 PCL 不兼容的加载器,允许选择跳过它的安装"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "删除:由于已不再需要,删除手动安装包下载功能"))
+            FeatureCount += 21
+            BugCount += 32
+        End If
+        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
+        If LastVersion < 359 Then 'Release 2.10.2
+            If LastVersion >= 357 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源包、光影包时能自动跳转到对应的文件夹"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:调整界面样式与动画,让整体视觉更干净,操作体验更顺滑"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法从 CurseForge 下载 Mod 等资源,或是安装整合包"))
+            FeatureCount += 28
+            BugCount += 28
+        End If
+        If LastVersion < 357 Then 'Release 2.10.0
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:下载资源包、光影包、数据包"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:允许设置文件下载源"))
+            FeatureCount += 9
+            BugCount += 26
+        End If
+        If LastVersion < 355 Then 'Release 2.9.3
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:Minecraft 会优先使用独立显卡运行"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:Minecraft 会优先使用独立显卡运行"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:简化下载新版本第二步的 UI"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:简化下载新版本第二步的 UI"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:使用新的版本隔离策略"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:使用新的版本隔离策略"))
@@ -66,7 +104,60 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '2:BUG* IMP-
         '1:BUG-
         '1:BUG-
-        If LastVersion < 353 Then 'Snapshot 2.9.3
+        If LastVersion < 370 Then 'Snapshot 2.10.8
+            If LastVersion >= 368 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化"))
+            FeatureCount += 3
+            BugCount += 4
+        End If
+        If LastVersion < 368 Then 'Snapshot 2.10.7
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "新增:允许在版本设置中设置禁止更新 Mod,以防整合包玩家误操作"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:若整合包需要 PCL 不兼容的加载器,允许选择跳过它的安装"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "删除:由于已不再需要,删除手动安装包下载功能"))
+            FeatureCount += 19
+            BugCount += 28
+        End If
+        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
+        If LastVersion < 360 Then 'Snapshot 2.10.2
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法从 CurseForge 下载 Mod 等资源,或是安装整合包"))
+            If LastVersion >= 358 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法加载正版皮肤的头像"))
+            FeatureCount += 3
+            BugCount += 5
+        End If
+        If LastVersion < 358 Then 'Snapshot 2.10.1
+            If LastVersion >= 356 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源包、光影包时能自动跳转到对应的文件夹"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:调整界面样式与动画,让整体视觉更干净,操作体验更顺滑"))
+            FeatureCount += 25
+            BugCount += 23
+        End If
+        If LastVersion < 356 Then 'Snapshot 2.10.0
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:下载资源包、光影包、数据包"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:允许设置文件下载源"))
+            FeatureCount += 9
+            BugCount += 26
+        End If
+        If LastVersion < 354 Then 'Snapshot 2.9.3
             If LastVersion = 352 Then
             If LastVersion = 352 Then
                 FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:低版本 MC 没有声音"))
                 FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:低版本 MC 没有声音"))
                 FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:若不安装 Mod 加载器,则无法安装 OptiFine 1.14+"))
                 FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:若不安装 Mod 加载器,则无法安装 OptiFine 1.14+"))
@@ -145,7 +236,7 @@ Public Class FormMain
         '整理更新日志文本
         '整理更新日志文本
         Dim ContentList As New List(Of String)
         Dim ContentList As New List(Of String)
         Dim SortedFeatures = FeatureList.OrderByDescending(Function(f) f.Key).ToList
         Dim SortedFeatures = FeatureList.OrderByDescending(Function(f) f.Key).ToList
-        If Not SortedFeatures.Any() AndAlso FeatureCount = 0 AndAlso BugCount = 0 Then ContentList.Add("龙猫忘记写更新日志啦!可以去提醒他一下……")
+        If Not SortedFeatures.Any() AndAlso FeatureCount = 0 AndAlso BugCount = 0 Then ContentList.Add("没有更新日志……")
         For i = 0 To Math.Min(9, SortedFeatures.Count - 1) '最多取 10 项
         For i = 0 To Math.Min(9, SortedFeatures.Count - 1) '最多取 10 项
             ContentList.Add(SortedFeatures(i).Value)
             ContentList.Add(SortedFeatures(i).Value)
         Next
         Next
@@ -188,7 +279,7 @@ Public Class FormMain
             If Not Setup.IsUnset("LaunchArgumentIndie") Then
             If Not Setup.IsUnset("LaunchArgumentIndie") Then
                 Log("[Start] 从老 PCL 迁移版本隔离")
                 Log("[Start] 从老 PCL 迁移版本隔离")
                 Setup.Set("LaunchArgumentIndieV2", Setup.Get("LaunchArgumentIndie"))
                 Setup.Set("LaunchArgumentIndieV2", Setup.Get("LaunchArgumentIndie"))
-            ElseIf Not Setup.IsUnset("WindowHeight") Then
+            ElseIf Not Setup.IsUnset("LaunchVersionSelect") Then
                 Log("[Start] 从老 PCL 升级,但此前未调整版本隔离,使用老的版本隔离默认值")
                 Log("[Start] 从老 PCL 升级,但此前未调整版本隔离,使用老的版本隔离默认值")
                 Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndie"))
                 Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndie"))
             Else
             Else
@@ -199,16 +290,24 @@ Public Class FormMain
         '刷新主题
         '刷新主题
         ThemeCheckAll(False)
         ThemeCheckAll(False)
         Setup.Load("UiLauncherTheme")
         Setup.Load("UiLauncherTheme")
+        '注册拖拽事件(不能直接加 Handles,否则没用;#6340)
+        [AddHandler](DragDrop.DragEnterEvent, New DragEventHandler(AddressOf HandleDrag), handledEventsToo:=True)
+        [AddHandler](DragDrop.DragOverEvent, New DragEventHandler(AddressOf HandleDrag), handledEventsToo:=True)
         '加载 UI
         '加载 UI
         InitializeComponent()
         InitializeComponent()
         Opacity = 0
         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(FrmLaunchLeft.Parent) Then FrmLaunchLeft.SetValue(ContentPresenter.ContentProperty, Nothing)
         If Not IsNothing(FrmLaunchRight.Parent) Then FrmLaunchRight.SetValue(ContentPresenter.ContentProperty, Nothing)
         If Not IsNothing(FrmLaunchRight.Parent) Then FrmLaunchRight.SetValue(ContentPresenter.ContentProperty, Nothing)
@@ -255,7 +354,7 @@ Public Class FormMain
         Resizer.addResizerUp(ResizerT)
         Resizer.addResizerUp(ResizerT)
         'PLC 彩蛋
         'PLC 彩蛋
         If RandomInteger(1, 1000) = 233 Then
         If RandomInteger(1, 1000) = 233 Then
-            ShapeTitleLogo.Data = New GeometryConverter().ConvertFromString("M26,29 v-25 h5 a7,7 180 0 1 0,14 h-5 M80,6.5 a10,11.5 180 1 0 0,18   M47,2.5 v24.5 h12   M98,2 v27   M107,2 v27")
+            ShapeTitleLogo.Data = New GeometryConverter().ConvertFromString("M26,29 v-25 h6 a7,7 180 0 1 0,14 h-6 M83,6.5 a10,11.5 180 1 0 0,18 M48,2.5 v24.5 h13.5")
         End If
         End If
         '加载窗口
         '加载窗口
         ThemeRefreshMain()
         ThemeRefreshMain()
@@ -311,8 +410,8 @@ Public Class FormMain
             '启动加载器池
             '启动加载器池
             Try
             Try
                 JavaListInit() '延后到同意协议后再执行,避免在初次启动时进行进程操作
                 JavaListInit() '延后到同意协议后再执行,避免在初次启动时进行进程操作
-                Thread.Sleep(200)
-                DlClientListMojangLoader.Start(1)
+                Thread.Sleep(100)
+                DlClientListMojangLoader.Start(1) 'PCL 会同时根据这里的加载结果决定是否使用官方源进行下载
                 RunCountSub()
                 RunCountSub()
                 ServerLoader.Start(1)
                 ServerLoader.Start(1)
                 RunInNewThread(AddressOf TryClearTaskTemp, "TryClearTaskTemp", ThreadPriority.BelowNormal)
                 RunInNewThread(AddressOf TryClearTaskTemp, "TryClearTaskTemp", ThreadPriority.BelowNormal)
@@ -325,7 +424,9 @@ Public Class FormMain
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "清理自动更新文件失败")
                 Log(ex, "清理自动更新文件失败")
             End Try
             End Try
-        End Sub, "Start Loader", ThreadPriority.Lowest)
+            '上报
+            Telemetry("启动")
+        End Sub, "初始化", ThreadPriority.Lowest)
 
 
         Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
         Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
     End Sub
     End Sub
@@ -421,8 +522,8 @@ Public Class FormMain
             Log("[Start] 已从老版本迁移 Mod 命名设置")
             Log("[Start] 已从老版本迁移 Mod 命名设置")
         End If
         End If
         '输出更新日志
         '输出更新日志
-        If LastVersionCode <= 0 Then Exit Sub
-        If LowerVersionCode >= VersionCode Then Exit Sub
+        If LastVersionCode <= 0 Then Return
+        If LowerVersionCode >= VersionCode Then Return
         ShowUpdateLog(LowerVersionCode)
         ShowUpdateLog(LowerVersionCode)
     End Sub
     End Sub
     Private Sub DowngradeSub(LastVersionCode As Integer)
     Private Sub DowngradeSub(LastVersionCode As Integer)
@@ -456,7 +557,7 @@ Public Class FormMain
                     Next
                     Next
                 End Sub, "强行停止下载任务")
                 End Sub, "强行停止下载任务")
             Else
             Else
-                Exit Sub
+                Return
             End If
             End If
         End If
         End If
         '关闭
         '关闭
@@ -496,26 +597,20 @@ Public Class FormMain
         On Error Resume Next
         On Error Resume Next
         IsProgramEnded = True
         IsProgramEnded = True
         AniControlEnabled += 1
         AniControlEnabled += 1
-        PageLinkIoi.IoiStop(False)
-        PageLinkHiper.HiperStop(False)
         If IsUpdateWaitingRestart Then UpdateRestart(False)
         If IsUpdateWaitingRestart Then UpdateRestart(False)
         If ReturnCode = ProcessReturnValues.Exception Then
         If ReturnCode = ProcessReturnValues.Exception Then
             If Not IsLogShown Then
             If Not IsLogShown Then
                 FeedbackInfo()
                 FeedbackInfo()
-                Log("请在 https://github.com/Hex-Dragon/PCL2/issues 提交错误报告,以便于作者解决此问题!")
+                Log("请在 https://github.com/Meloong-Git/PCL/issues 提交错误报告,以便于作者解决此问题!")
                 IsLogShown = True
                 IsLogShown = True
-                ShellOnly(Path & "PCL\Log1.txt")
+                StartProcess(Path & "PCL\Log1.txt")
             End If
             End If
             Thread.Sleep(500) '防止 PCL 在记事本打开前就被掐掉
             Thread.Sleep(500) '防止 PCL 在记事本打开前就被掐掉
         End If
         End If
-        Log("[System] 程序已退出,返回值:" & GetStringFromEnum(CType(ReturnCode, [Enum])))
+        Log("[System] 程序已退出,返回值:" & GetStringFromEnum(ReturnCode))
         LogFlush()
         LogFlush()
-        If ReturnCode = ProcessReturnValues.Success Then
-            Process.GetCurrentProcess.Kill()
-        Else
-            Environment.Exit(ReturnCode)
-            Process.GetCurrentProcess.Kill()
-        End If
+        If ReturnCode <> ProcessReturnValues.Success Then Environment.Exit(ReturnCode)
+        Process.GetCurrentProcess.Kill()
     End Sub
     End Sub
     Private Sub BtnTitleClose_Click(sender As Object, e As RoutedEventArgs) Handles BtnTitleClose.Click
     Private Sub BtnTitleClose_Click(sender As Object, e As RoutedEventArgs) Handles BtnTitleClose.Click
         EndProgram(True)
         EndProgram(True)
@@ -556,12 +651,13 @@ Public Class FormMain
 
 
     '按键事件
     '按键事件
     Private Sub FormMain_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
     Private Sub FormMain_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
-        If e.IsRepeat Then Exit Sub
-        '调用弹窗:回车选择第一个,Esc 选择最后一个
-        If PanMsg.Children.Count > 0 Then
+        If e.IsRepeat Then Return
+        '修复按下 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
             If e.Key = Key.Enter Then
                 CType(PanMsg.Children(0), Object).Btn1_Click()
                 CType(PanMsg.Children(0), Object).Btn1_Click()
-                Exit Sub
             ElseIf e.Key = Key.Escape Then
             ElseIf e.Key = Key.Escape Then
                 Dim Msg As Object = PanMsg.Children(0)
                 Dim Msg As Object = PanMsg.Children(0)
                 If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then
                 If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then
@@ -571,16 +667,21 @@ Public Class FormMain
                 Else
                 Else
                     Msg.Btn1_Click()
                     Msg.Btn1_Click()
                 End If
                 End If
-                Exit Sub
             End If
             End If
+            Return
         End If
         End If
+
+        '==========================
+        ' 在没有弹窗时:继续检查……
+        '==========================
+
         '按 ESC 返回上一级
         '按 ESC 返回上一级
         If e.Key = Key.Escape Then TriggerPageBack()
         If e.Key = Key.Escape Then TriggerPageBack()
         '更改隐藏版本可见性
         '更改隐藏版本可见性
         If e.Key = Key.F11 AndAlso PageCurrent = FormMain.PageType.VersionSelect Then
         If e.Key = Key.F11 AndAlso PageCurrent = FormMain.PageType.VersionSelect Then
             FrmSelectRight.ShowHidden = Not FrmSelectRight.ShowHidden
             FrmSelectRight.ShowHidden = Not FrmSelectRight.ShowHidden
             LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
             LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
-            Exit Sub
+            Return
         End If
         End If
         '更改功能隐藏可见性
         '更改功能隐藏可见性
         If e.Key = Key.F12 Then
         If e.Key = Key.F12 Then
@@ -591,13 +692,13 @@ Public Class FormMain
                 Hint("功能隐藏设置已重新开启!", HintType.Finish)
                 Hint("功能隐藏设置已重新开启!", HintType.Finish)
             End If
             End If
             PageSetupUI.HiddenRefresh()
             PageSetupUI.HiddenRefresh()
-            Exit Sub
+            Return
         End If
         End If
         '按 F5 刷新页面
         '按 F5 刷新页面
         If e.Key = Key.F5 Then
         If e.Key = Key.F5 Then
             If TypeOf PageLeft Is IRefreshable Then CType(PageLeft, IRefreshable).Refresh()
             If TypeOf PageLeft Is IRefreshable Then CType(PageLeft, IRefreshable).Refresh()
             If TypeOf PageRight Is IRefreshable Then CType(PageRight, IRefreshable).Refresh()
             If TypeOf PageRight Is IRefreshable Then CType(PageRight, IRefreshable).Refresh()
-            Exit Sub
+            Return
         End If
         End If
         '调用启动游戏
         '调用启动游戏
         If e.Key = Key.Enter AndAlso PageCurrent = FormMain.PageType.Launch Then
         If e.Key = Key.Enter AndAlso PageCurrent = FormMain.PageType.Launch Then
@@ -607,12 +708,10 @@ Public Class FormMain
                 FrmLaunchLeft.LaunchButtonClick()
                 FrmLaunchLeft.LaunchButtonClick()
             End If
             End If
         End If
         End If
-        '修复按下 Alt 后误认为弹出系统菜单导致的冻结
-        If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True
     End Sub
     End Sub
     Private Sub FormMain_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseDown
     Private Sub FormMain_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseDown
         '鼠标侧键返回上一级
         '鼠标侧键返回上一级
-        If FrmMain.PanMsg.Children.Count > 0 OrElse WaitingMyMsgBox.Any Then Exit Sub '弹窗中(#5513)
+        If FrmMain.PanMsg.Children.Count > 0 OrElse WaitingMyMsgBox.Any Then Return '弹窗中(#5513)
         If e.ChangedButton = MouseButton.XButton1 OrElse e.ChangedButton = MouseButton.XButton2 Then TriggerPageBack()
         If e.ChangedButton = MouseButton.XButton1 OrElse e.ChangedButton = MouseButton.XButton2 Then TriggerPageBack()
     End Sub
     End Sub
     Private Sub TriggerPageBack()
     Private Sub TriggerPageBack()
@@ -639,15 +738,41 @@ Public Class FormMain
     End Sub
     End Sub
 
 
     '文件拖放
     '文件拖放
-    Private Sub FrmMain_PreviewDragOver(sender As Object, e As DragEventArgs) Handles Me.PreviewDragOver
-        If e.Data.GetFormats.Contains("FileDrop") Then
-            e.Effects = DragDropEffects.Link
-        Else
+    Private Sub HandleDrag(sender As Object, e As DragEventArgs)
+        Try
+            If e.Handled AndAlso (e.Effects <> DragDropEffects.None) Then Return
+            e.Handled = True
+            '缓存
+            Static PrevData As IDataObject, PrevEffects As DragDropEffects
+            If e.Data Is PrevData Then
+                e.Effects = PrevEffects
+                Return
+            End If
+            '确定拖放效果
             e.Effects = DragDropEffects.None
             e.Effects = DragDropEffects.None
-        End If
+            If e.Data.GetDataPresent(DataFormats.Text) Then
+                Dim Str As String = e.Data.GetData(DataFormats.Text)
+                If Str.StartsWithF("authlib-injector:yggdrasil-server:") Then
+                    e.Effects = DragDropEffects.Copy
+                ElseIf Str.StartsWithF("file:///") Then
+                    e.Effects = DragDropEffects.Copy
+                End If
+            ElseIf e.Data.GetDataPresent(DataFormats.FileDrop) Then
+                Dim Files As String() = e.Data.GetData(DataFormats.FileDrop)
+                If Files IsNot Nothing AndAlso Files.Length > 0 Then
+                    e.Effects = DragDropEffects.Link
+                End If
+            End If
+            PrevData = e.Data
+            PrevEffects = e.Effects
+            Log("[System] 设置拖放类型:" & GetStringFromEnum(e.Effects))
+        Catch ex As Exception
+            Log(ex, "处理拖放时出错", LogLevel.Feedback)
+        End Try
     End Sub
     End Sub
-    Private Sub FrmMain_Drop(sender As Object, e As DragEventArgs) Handles Me.PreviewDrop
+    Private Sub FrmMain_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop
         Try
         Try
+            ShowWindowToTop()
             If e.Data.GetDataPresent(DataFormats.Text) Then
             If e.Data.GetDataPresent(DataFormats.Text) Then
                 '获取文本
                 '获取文本
                 Try
                 Try
@@ -656,22 +781,23 @@ Public Class FormMain
                     If Str.StartsWithF("authlib-injector:yggdrasil-server:") Then
                     If Str.StartsWithF("authlib-injector:yggdrasil-server:") Then
                         'Authlib 拖拽
                         'Authlib 拖拽
                         e.Handled = True
                         e.Handled = True
+                        e.Effects = DragDropEffects.Copy
                         Dim AuthlibServer As String = Net.WebUtility.UrlDecode(Str.Substring("authlib-injector:yggdrasil-server:".Length))
                         Dim AuthlibServer As String = Net.WebUtility.UrlDecode(Str.Substring("authlib-injector:yggdrasil-server:".Length))
                         Log("[System] Authlib 拖拽:" & AuthlibServer)
                         Log("[System] Authlib 拖拽:" & AuthlibServer)
                         If Not String.IsNullOrEmpty(New ValidateHttp().Validate(AuthlibServer)) Then
                         If Not String.IsNullOrEmpty(New ValidateHttp().Validate(AuthlibServer)) Then
                             Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Critical)
                             Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Critical)
-                            Exit Sub
+                            Return
                         End If
                         End If
                         Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent)
                         Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent)
                         If TargetVersion Is Nothing Then
                         If TargetVersion Is Nothing Then
                             Hint("请先下载游戏,再设置第三方登录!", HintType.Critical)
                             Hint("请先下载游戏,再设置第三方登录!", HintType.Critical)
-                            Exit Sub
+                            Return
                         End If
                         End If
                         If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then
                         If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then
                             'LittleSkin
                             'LittleSkin
                             If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启 LittleSkin 登录?" & vbCrLf &
                             If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启 LittleSkin 登录?" & vbCrLf &
                                         "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then
                                         "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then
-                                Exit Sub
+                                Return
                             End If
                             End If
                             Setup.Set("VersionServerLogin", 4, Version:=TargetVersion)
                             Setup.Set("VersionServerLogin", 4, Version:=TargetVersion)
                             Setup.Set("VersionServerAuthServer", "https://littleskin.cn/api/yggdrasil", Version:=TargetVersion)
                             Setup.Set("VersionServerAuthServer", "https://littleskin.cn/api/yggdrasil", Version:=TargetVersion)
@@ -682,7 +808,7 @@ Public Class FormMain
                             If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启第三方登录?" & vbCrLf &
                             If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启第三方登录?" & vbCrLf &
                                         $"登录服务器:{AuthlibServer}" & vbCrLf & vbCrLf &
                                         $"登录服务器:{AuthlibServer}" & vbCrLf & vbCrLf &
                                         "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then
                                         "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then
-                                Exit Sub
+                                Return
                             End If
                             End If
                             Setup.Set("VersionServerLogin", 4, Version:=TargetVersion)
                             Setup.Set("VersionServerLogin", 4, Version:=TargetVersion)
                             Setup.Set("VersionServerAuthServer", AuthlibServer, Version:=TargetVersion)
                             Setup.Set("VersionServerAuthServer", AuthlibServer, Version:=TargetVersion)
@@ -700,20 +826,22 @@ Public Class FormMain
                         '文件拖拽(例如从浏览器下载窗口拖入)
                         '文件拖拽(例如从浏览器下载窗口拖入)
                         Dim FilePath = Net.WebUtility.UrlDecode(Str).Substring("file:///".Length).Replace("/", "\")
                         Dim FilePath = Net.WebUtility.UrlDecode(Str).Substring("file:///".Length).Replace("/", "\")
                         e.Handled = True
                         e.Handled = True
+                        e.Effects = DragDropEffects.Copy
                         FileDrag(New List(Of String) From {FilePath})
                         FileDrag(New List(Of String) From {FilePath})
                     End If
                     End If
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "无法接取文本拖拽事件", LogLevel.Developer)
                     Log(ex, "无法接取文本拖拽事件", LogLevel.Developer)
-                    Exit Sub
+                    Return
                 End Try
                 End Try
             ElseIf e.Data.GetDataPresent(DataFormats.FileDrop) Then
             ElseIf e.Data.GetDataPresent(DataFormats.FileDrop) Then
                 '获取文件并检查
                 '获取文件并检查
                 Dim FilePathRaw = e.Data.GetData(DataFormats.FileDrop)
                 Dim FilePathRaw = e.Data.GetData(DataFormats.FileDrop)
                 If FilePathRaw Is Nothing Then '#2690
                 If FilePathRaw Is Nothing Then '#2690
                     Hint("请将文件解压后再拖入!", HintType.Critical)
                     Hint("请将文件解压后再拖入!", HintType.Critical)
-                    Exit Sub
+                    Return
                 End If
                 End If
                 e.Handled = True
                 e.Handled = True
+                e.Effects = DragDropEffects.Link
                 FileDrag(CType(FilePathRaw, IEnumerable(Of String)))
                 FileDrag(CType(FilePathRaw, IEnumerable(Of String)))
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception
@@ -728,10 +856,10 @@ Public Class FormMain
             '基础检查
             '基础检查
             If Directory.Exists(FilePathList.First) AndAlso Not File.Exists(FilePathList.First) Then
             If Directory.Exists(FilePathList.First) AndAlso Not File.Exists(FilePathList.First) Then
                 Hint("请拖入一个文件,而非文件夹!", HintType.Critical)
                 Hint("请拖入一个文件,而非文件夹!", HintType.Critical)
-                Exit Sub
+                Return
             ElseIf Not File.Exists(FilePathList.First) Then
             ElseIf Not File.Exists(FilePathList.First) Then
                 Hint("拖入的文件不存在:" & FilePathList.First, HintType.Critical)
                 Hint("拖入的文件不存在:" & FilePathList.First, HintType.Critical)
-                Exit Sub
+                Return
             End If
             End If
             '多文件拖拽
             '多文件拖拽
             If FilePathList.Count > 1 Then
             If FilePathList.Count > 1 Then
@@ -739,17 +867,17 @@ Public Class FormMain
                 For Each File In FilePathList
                 For Each File In FilePathList
                     If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").ToLower) Then
                     If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").ToLower) Then
                         Hint("一次请只拖入一个文件!", HintType.Critical)
                         Hint("一次请只拖入一个文件!", HintType.Critical)
-                        Exit Sub
+                        Return
                     End If
                     End If
                 Next
                 Next
             End If
             End If
-            '自定义主页
+            '主页
             Dim Extension As String = FilePath.AfterLast(".").ToLower
             Dim Extension As String = FilePath.AfterLast(".").ToLower
             If Extension = "xaml" Then
             If Extension = "xaml" Then
-                Log("[System] 文件后缀为 XAML,作为自定义主页加载")
+                Log("[System] 文件后缀为 XAML,作为主页加载")
                 If File.Exists(Path & "PCL\Custom.xaml") Then
                 If File.Exists(Path & "PCL\Custom.xaml") Then
-                    If MyMsgBox("已存在一个自定义主页文件,是否要将它覆盖?", "覆盖确认", "覆盖", "取消") = 2 Then
-                        Exit Sub
+                    If MyMsgBox("已存在一个主页文件,是否要将它覆盖?", "覆盖确认", "覆盖", "取消") = 2 Then
+                        Return
                     End If
                     End If
                 End If
                 End If
                 CopyFile(FilePath, Path & "PCL\Custom.xaml")
                 CopyFile(FilePath, Path & "PCL\Custom.xaml")
@@ -759,18 +887,18 @@ Public Class FormMain
                     FrmLaunchRight.ForceRefresh()
                     FrmLaunchRight.ForceRefresh()
                     Hint("已加载主页自定义文件!", HintType.Finish)
                     Hint("已加载主页自定义文件!", HintType.Finish)
                 End Sub)
                 End Sub)
-                Exit Sub
+                Return
             End If
             End If
             '安装 Mod
             '安装 Mod
-            If PageVersionMod.InstallMods(FilePathList) Then Exit Sub
+            If PageVersionMod.InstallMods(FilePathList) Then Return
             '安装整合包
             '安装整合包
             If {"zip", "rar", "mrpack"}.Any(Function(t) t = Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试
             If {"zip", "rar", "mrpack"}.Any(Function(t) t = Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试
                 Log("[System] 文件为压缩包,尝试作为整合包安装")
                 Log("[System] 文件为压缩包,尝试作为整合包安装")
                 Try
                 Try
                     ModpackInstall(FilePath)
                     ModpackInstall(FilePath)
-                    Exit Sub
+                    Return
                 Catch ex As CancelledException
                 Catch ex As CancelledException
-                    Exit Sub '用户主动取消
+                    Return '用户主动取消
                 Catch ex As Exception
                 Catch ex As Exception
                     '安装失败,继续往后尝试
                     '安装失败,继续往后尝试
                 End Try
                 End Try
@@ -778,17 +906,17 @@ Public Class FormMain
             'RAR 处理
             'RAR 处理
             If Extension = "rar" Then
             If Extension = "rar" Then
                 Hint("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试!")
                 Hint("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试!")
-                Exit Sub
+                Return
             End If
             End If
             '错误报告分析
             '错误报告分析
             Try
             Try
                 Log("[System] 尝试进行错误报告分析")
                 Log("[System] 尝试进行错误报告分析")
-                Dim Analyzer As New CrashAnalyzer(GetUuid())
+                Dim Analyzer As New CrashAnalyzer
                 Analyzer.Import(FilePath)
                 Analyzer.Import(FilePath)
-                If Analyzer.Prepare() = 0 Then Exit Try
+                If Not Analyzer.Prepare() Then Exit Try
                 Analyzer.Analyze()
                 Analyzer.Analyze()
                 Analyzer.Output(True, New List(Of String))
                 Analyzer.Output(True, New List(Of String))
-                Exit Sub
+                Return
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "自主错误报告分析失败", LogLevel.Feedback)
                 Log(ex, "自主错误报告分析失败", LogLevel.Feedback)
             End Try
             End Try
@@ -828,7 +956,7 @@ Public Class FormMain
             Return _Hidden
             Return _Hidden
         End Get
         End Get
         Set(value As Boolean)
         Set(value As Boolean)
-            If _Hidden = value Then Exit Property
+            If _Hidden = value Then Return
             _Hidden = value
             _Hidden = value
             If value Then
             If value Then
                 '隐藏
                 '隐藏
@@ -918,15 +1046,12 @@ Public Class FormMain
     ''' </summary>
     ''' </summary>
     Public Enum PageSubType
     Public Enum PageSubType
         [Default] = 0
         [Default] = 0
-        DownloadInstall = 1
-        DownloadClient = 4
-        DownloadOptiFine = 5
-        DownloadForge = 6
-        DownloadNeoForge = 7
-        DownloadFabric = 8
-        DownloadLiteLoader = 9
-        DownloadMod = 11
-        DownloadPack = 12
+        DownloadInstall = 0
+        DownloadMod = 2
+        DownloadPack = 3
+        DownloadDataPack = 4
+        DownloadResourcePack = 5
+        DownloadShader = 6
         SetupLaunch = 0
         SetupLaunch = 0
         SetupUI = 1
         SetupUI = 1
         SetupSystem = 2
         SetupSystem = 2
@@ -957,18 +1082,9 @@ Public Class FormMain
             Case PageType.VersionSetup
             Case PageType.VersionSetup
                 Return "版本设置 - " & If(PageVersionLeft.Version Is Nothing, "未知版本", PageVersionLeft.Version.Name)
                 Return "版本设置 - " & If(PageVersionLeft.Version Is Nothing, "未知版本", PageVersionLeft.Version.Name)
             Case PageType.CompDetail
             Case PageType.CompDetail
-                Dim Project As CompProject = Stack.Additional(0)
-                Select Case Project.Type
-                    Case CompType.Mod
-                        Return "Mod 下载 - " & Project.TranslatedName
-                    Case CompType.ModPack
-                        Return "整合包下载 - " & Project.TranslatedName
-                    Case Else 'CompType.ResourcePack
-                        Return "资源包下载 - " & Project.TranslatedName
-                End Select
+                Return "资源下载 - " & CType(Stack.Additional(0), CompProject).TranslatedName
             Case PageType.HelpDetail
             Case PageType.HelpDetail
-                Dim Entry As HelpEntry = Stack.Additional(0)
-                Return Entry.Title
+                Return CType(Stack.Additional(0), HelpEntry).Title
             Case Else
             Case Else
                 Return ""
                 Return ""
         End Select
         End Select
@@ -1097,7 +1213,7 @@ Public Class FormMain
     ''' 通过点击导航栏改变页面。
     ''' 通过点击导航栏改变页面。
     ''' </summary>
     ''' </summary>
     Private Sub BtnTitleSelect_Click(sender As MyRadioButton, raiseByMouse As Boolean) Handles BtnTitleSelect0.Check, BtnTitleSelect1.Check, BtnTitleSelect2.Check, BtnTitleSelect3.Check, BtnTitleSelect4.Check
     Private Sub BtnTitleSelect_Click(sender As MyRadioButton, raiseByMouse As Boolean) Handles BtnTitleSelect0.Check, BtnTitleSelect1.Check, BtnTitleSelect2.Check, BtnTitleSelect3.Check, BtnTitleSelect4.Check
-        If IsChangingPage Then Exit Sub
+        If IsChangingPage Then Return
         PageChangeActual(Val(sender.Tag))
         PageChangeActual(Val(sender.Tag))
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
@@ -1116,7 +1232,7 @@ Public Class FormMain
     ''' 切换现有页面的实际方法。
     ''' 切换现有页面的实际方法。
     ''' </summary>
     ''' </summary>
     Private Sub PageChangeActual(Stack As PageStackData, Optional SubType As PageSubType = -1)
     Private Sub PageChangeActual(Stack As PageStackData, Optional SubType As PageSubType = -1)
-        If PageCurrent = Stack AndAlso (PageCurrentSub = SubType OrElse SubType = -1) Then Exit Sub
+        If PageCurrent = Stack AndAlso (PageCurrentSub = SubType OrElse SubType = -1) Then Return
         AniControlEnabled += 1
         AniControlEnabled += 1
         Try
         Try
 
 
@@ -1235,14 +1351,14 @@ Public Class FormMain
                 PanMainLeft.Background = Nothing
                 PanMainLeft.Background = Nothing
                 AniControlEnabled -= 1
                 AniControlEnabled -= 1
                 RunInUi(Sub() PanMainLeft_Resize(PanMainLeft.ActualWidth), True)
                 RunInUi(Sub() PanMainLeft_Resize(PanMainLeft.ActualWidth), True)
-            End Sub, 130),
+            End Sub, 110),
             AaCode(
             AaCode(
             Sub()
             Sub()
                 '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
                 '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
                 PageLeft.Opacity = 1
                 PageLeft.Opacity = 1
                 PageLeft.TriggerShowAnimation()
                 PageLeft.TriggerShowAnimation()
             End Sub, 30, True)
             End Sub, 30, True)
-            }, "FrmMain PageChangeLeft")
+        }, "FrmMain PageChangeLeft")
         AniStart({
         AniStart({
             AaCode(
             AaCode(
             Sub()
             Sub()
@@ -1254,14 +1370,14 @@ Public Class FormMain
                 PanMainRight.Background = Nothing
                 PanMainRight.Background = Nothing
                 AniControlEnabled -= 1
                 AniControlEnabled -= 1
                 RunInUi(Sub() BtnExtraBack.ShowRefresh(), True)
                 RunInUi(Sub() BtnExtraBack.ShowRefresh(), True)
-            End Sub, 130),
+            End Sub, 110),
             AaCode(
             AaCode(
             Sub()
             Sub()
                 '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
                 '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
                 PageRight.Opacity = 1
                 PageRight.Opacity = 1
                 PageRight.PageOnEnter()
                 PageRight.PageOnEnter()
             End Sub, 30, True)
             End Sub, 30, True)
-            }, "FrmMain PageChangeRight")
+        }, "FrmMain PageChangeRight")
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 退出子界面。
     ''' 退出子界面。
@@ -1287,7 +1403,7 @@ Public Class FormMain
 
 
     '左边栏改变
     '左边栏改变
     Private Sub PanMainLeft_SizeChanged(sender As Object, e As SizeChangedEventArgs) Handles PanMainLeft.SizeChanged
     Private Sub PanMainLeft_SizeChanged(sender As Object, e As SizeChangedEventArgs) Handles PanMainLeft.SizeChanged
-        If Not e.WidthChanged Then Exit Sub
+        If Not e.WidthChanged Then Return
         PanMainLeft_Resize(e.NewSize.Width)
         PanMainLeft_Resize(e.NewSize.Width)
     End Sub
     End Sub
     Private Sub PanMainLeft_Resize(NewWidth As Double)
     Private Sub PanMainLeft_Resize(NewWidth As Double)
@@ -1297,16 +1413,16 @@ Public Class FormMain
             If NewWidth > 0 Then
             If NewWidth > 0 Then
                 '宽度足够,显示
                 '宽度足够,显示
                 AniStart({
                 AniStart({
-                    AaWidth(RectLeftBackground, NewWidth - RectLeftBackground.Width, 400,, New AniEaseOutFluent(AniEasePower.ExtraStrong)),
-                    AaOpacity(RectLeftShadow, 1 - RectLeftShadow.Opacity, 200),
-                    AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 250)
+                    AaWidth(RectLeftBackground, NewWidth - RectLeftBackground.Width, 180,, New AniEaseOutFluent(AniEasePower.ExtraStrong)),
+                    AaOpacity(RectLeftShadow, 1 - RectLeftShadow.Opacity, 180),
+                    AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 150)
                 }, "FrmMain LeftChange", True)
                 }, "FrmMain LeftChange", True)
             Else
             Else
                 '宽度不足,隐藏
                 '宽度不足,隐藏
                 AniStart({
                 AniStart({
-                    AaWidth(RectLeftBackground, -RectLeftBackground.Width, 200,, New AniEaseOutFluent),
-                    AaOpacity(RectLeftShadow, -RectLeftShadow.Opacity, 200),
-                    AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 170)
+                    AaWidth(RectLeftBackground, -RectLeftBackground.Width, 180,, New AniEaseOutFluent),
+                    AaOpacity(RectLeftShadow, -RectLeftShadow.Opacity, 180),
+                    AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 150)
                 }, "FrmMain LeftChange", True)
                 }, "FrmMain LeftChange", True)
             End If
             End If
         Else
         Else
@@ -1322,14 +1438,14 @@ Public Class FormMain
 
 
     '在时钟中调用,使得即使鼠标在窗口外松开,也可以释放控件
     '在时钟中调用,使得即使鼠标在窗口外松开,也可以释放控件
     Public Sub DragTick()
     Public Sub DragTick()
-        If DragControl Is Nothing Then Exit Sub
+        If DragControl Is Nothing Then Return
         If Not Mouse.LeftButton = MouseButtonState.Pressed Then
         If Not Mouse.LeftButton = MouseButtonState.Pressed Then
             DragStop()
             DragStop()
         End If
         End If
     End Sub
     End Sub
     '在鼠标移动时调用,以改变 Slider 位置
     '在鼠标移动时调用,以改变 Slider 位置
     Public Sub DragDoing() Handles PanBack.MouseMove
     Public Sub DragDoing() Handles PanBack.MouseMove
-        If DragControl Is Nothing Then Exit Sub
+        If DragControl Is Nothing Then Return
         If Mouse.LeftButton = MouseButtonState.Pressed Then
         If Mouse.LeftButton = MouseButtonState.Pressed Then
             DragControl.DragDoing()
             DragControl.DragDoing()
         Else
         Else
@@ -1339,7 +1455,7 @@ Public Class FormMain
     Public Sub DragStop()
     Public Sub DragStop()
         '存在其他线程调用的可能性,因此需要确保在 UI 线程运行
         '存在其他线程调用的可能性,因此需要确保在 UI 线程运行
         RunInUi(Sub()
         RunInUi(Sub()
-                    If DragControl Is Nothing Then Exit Sub
+                    If DragControl Is Nothing Then Return
                     Dim Control = DragControl
                     Dim Control = DragControl
                     DragControl = Nothing
                     DragControl = Nothing
                     Control.DragStop() '控件会在该事件中判断 DragControl,所以得放在后面
                     Control.DragStop() '控件会在该事件中判断 DragControl,所以得放在后面
@@ -1413,7 +1529,8 @@ Public Class FormMain
     End Function
     End Function
     Private Function BtnExtraBack_GetRealChild() As MyScrollViewer
     Private Function BtnExtraBack_GetRealChild() As MyScrollViewer
         If PanMainRight.Child Is Nothing OrElse TypeOf PanMainRight.Child IsNot MyPageRight Then Return Nothing
         If PanMainRight.Child Is Nothing OrElse TypeOf PanMainRight.Child IsNot MyPageRight Then Return Nothing
-        Return CType(PanMainRight.Child, MyPageRight).PanScroll
+        Dim Page As MyPageRight = PanMainRight.Child
+        Return Page.FindName(Page.PanScroll)
     End Function
     End Function
 
 
 #End Region
 #End Region

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

@@ -624,6 +624,26 @@ Public Module ModAnimation
             Return Ease.GetValue(t)
             Return Ease.GetValue(t)
         End Function
         End Function
     End Class
     End Class
+    ''' <summary>
+    ''' 以特定速度开始的平滑结束。
+    ''' </summary>
+    Public Class AniEaseOutFluentWithInitial
+        Inherits AniEase
+        Private ReadOnly alpha As Double '(初速度 / 平均速度) – 1
+        ''' <param name="InitialPixelPerSecond">初速度,px/s</param>
+        ''' <param name="TotalSecond">总时长,s</param>
+        ''' <param name="TotalDistance">总路程,px</param>
+        Public Sub New(InitialPixelPerSecond As Double, TotalSecond As Double, TotalDistance As Double)
+            Dim v0_norm As Double = InitialPixelPerSecond * TotalSecond / TotalDistance '归一化初速度
+            alpha = v0_norm - 1.0
+            If alpha < 0 Then alpha = 0 '初速度小于平均速度时,退化为线性
+        End Sub
+        Public Overrides Function GetValue(percent As Double) As Double
+            Dim p As Double = MathClamp(percent, 0, 1)
+            If alpha = 0 Then Return p '退化到线性
+            Return (alpha + 1) * p / (1 + alpha * p)
+        End Function
+    End Class
 
 
     'Back / 回弹
     'Back / 回弹
     ''' <summary>
     ''' <summary>
@@ -807,7 +827,7 @@ Sleeper:
                                    Thread.Sleep(1)
                                    Thread.Sleep(1)
                                Loop
                                Loop
                            Catch ex As Exception
                            Catch ex As Exception
-                               Log(ex, "动画帧执行失败", LogLevel.Assert)
+                               Log(ex, "动画帧执行失败", LogLevel.Critical)
                            End Try
                            End Try
                        End Sub, "Animation", ThreadPriority.AboveNormal)
                        End Sub, "Animation", ThreadPriority.AboveNormal)
     End Sub
     End Sub

+ 180 - 190
Plain Craft Launcher 2/Modules/Base/ModBase.vb

@@ -1,6 +1,6 @@
 Imports System.Globalization
 Imports System.Globalization
 Imports System.IO.Compression
 Imports System.IO.Compression
-Imports System.Reflection
+Imports System.Net.Sockets
 Imports System.Runtime.CompilerServices
 Imports System.Runtime.CompilerServices
 Imports System.Security.Cryptography
 Imports System.Security.Cryptography
 Imports System.Security.Principal
 Imports System.Security.Principal
@@ -13,13 +13,13 @@ Public Module ModBase
 #Region "声明"
 #Region "声明"
 
 
     '下列版本信息由更新器自动修改
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.9.3" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.9.3." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.10.8" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.10.8." & VersionBranchCode '标准格式的四段式版本号
     Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
     Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
 #If BETA Then
 #If BETA Then
-    Public Const VersionCode As Integer = 355 'Release
+    Public Const VersionCode As Integer = 367 'Release
 #Else
 #Else
-    Public Const VersionCode As Integer = 354 'Snapshot
+    Public Const VersionCode As Integer = 370 'Snapshot
 #End If
 #End If
     '自动生成的版本信息
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -745,7 +745,6 @@ Public Module ModBase
             Dim IsRight As Boolean = FilePath.EndsWithF("\")
             Dim IsRight As Boolean = FilePath.EndsWithF("\")
             FilePath = Left(FilePath, Len(FilePath) - 1)
             FilePath = Left(FilePath, Len(FilePath) - 1)
             GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"})) & If(IsRight, "\", "/")
             GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"})) & If(IsRight, "\", "/")
-            If GetPathFromFullPath = "" Then Throw New Exception("不包含路径:" & FilePath)
         Else
         Else
             '是文件路径
             '是文件路径
             GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"}) + 1)
             GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"}) + 1)
@@ -766,17 +765,6 @@ Public Module ModBase
         Return FilePath
         Return FilePath
     End Function
     End Function
     ''' <summary>
     ''' <summary>
-    ''' 从文件路径或者 Url 获取不包含路径与扩展名的文件名。不包含文件名将会抛出异常。
-    ''' </summary>
-    Public Function GetFileNameWithoutExtentionFromPath(FilePath As String) As String
-        Dim Name As String = GetFileNameFromPath(FilePath)
-        If Name.Contains(".") Then
-            Return Name.Substring(0, Name.LastIndexOfF("."))
-        Else
-            Return Name
-        End If
-    End Function
-    ''' <summary>
     ''' 从文件夹路径获取文件夹名。
     ''' 从文件夹路径获取文件夹名。
     ''' </summary>
     ''' </summary>
     Public Function GetFolderNameFromPath(FolderPath As String) As String
     Public Function GetFolderNameFromPath(FolderPath As String) As String
@@ -786,13 +774,16 @@ Public Module ModBase
     End Function
     End Function
 
 
     '读取、写入、复制文件
     '读取、写入、复制文件
+    ''' <summary>
+    ''' 复制文件。会自动创建文件夹、会覆盖已有的文件。
+    ''' </summary>
     Public Sub CopyFile(FromPath As String, ToPath As String)
     Public Sub CopyFile(FromPath As String, ToPath As String)
         Try
         Try
             '还原文件路径
             '还原文件路径
-            If Not FromPath.Contains(":\") Then FromPath = Path & FromPath
-            If Not ToPath.Contains(":\") Then ToPath = Path & ToPath
+            FromPath = If(FromPath.Contains(":\"), FromPath, Path & FromPath)
+            ToPath = If(ToPath.Contains(":\"), ToPath, Path & ToPath)
             '如果复制同一个文件则跳过
             '如果复制同一个文件则跳过
-            If FromPath = ToPath Then Exit Sub
+            If FromPath = ToPath Then Return
             '确保目录存在
             '确保目录存在
             Directory.CreateDirectory(GetPathFromFullPath(ToPath))
             Directory.CreateDirectory(GetPathFromFullPath(ToPath))
             '复制文件
             '复制文件
@@ -804,7 +795,6 @@ Public Module ModBase
     ''' <summary>
     ''' <summary>
     ''' 读取文件,如果失败则返回空数组。
     ''' 读取文件,如果失败则返回空数组。
     ''' </summary>
     ''' </summary>
-    ''' <param name="FilePath">文件完整或相对路径。</param>
     Public Function ReadFileBytes(FilePath As String, Optional Encoding As Encoding = Nothing) As Byte()
     Public Function ReadFileBytes(FilePath As String, Optional Encoding As Encoding = Nothing) As Byte()
         Try
         Try
             '还原文件路径
             '还原文件路径
@@ -860,7 +850,7 @@ Public Module ModBase
     ''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
     ''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
     Public Sub WriteFile(FilePath As String, Text As String, Optional Append As Boolean = False, Optional Encoding As Encoding = Nothing)
     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 = If(FilePath.Contains(":\"), FilePath, Path & FilePath)
         '确保目录存在
         '确保目录存在
         Directory.CreateDirectory(GetPathFromFullPath(FilePath))
         Directory.CreateDirectory(GetPathFromFullPath(FilePath))
         '写入文件
         '写入文件
@@ -885,7 +875,7 @@ Public Module ModBase
     ''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
     ''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
     Public Sub WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False)
     Public Sub WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False)
         '处理相对路径
         '处理相对路径
-        If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
+        FilePath = If(FilePath.Contains(":\"), FilePath, Path & FilePath)
         '确保目录存在
         '确保目录存在
         Directory.CreateDirectory(GetPathFromFullPath(FilePath))
         Directory.CreateDirectory(GetPathFromFullPath(FilePath))
         '写入文件
         '写入文件
@@ -1282,7 +1272,7 @@ Re:
                 Return Nothing
                 Return Nothing
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "检查文件出错")
                 Log(ex, "检查文件出错")
-                Return GetExceptionSummary(ex)
+                Return ex.GetBrief()
             End Try
             End Try
         End Function
         End Function
     End Class
     End Class
@@ -1308,16 +1298,13 @@ Re:
         Else
         Else
             '以 zip 方式解压
             '以 zip 方式解压
             Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030")))
             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
                 For Each Entry As ZipArchiveEntry In Archive.Entries
-                    If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / TotalCount)
+                    If ProgressIncrementHandler IsNot Nothing AndAlso TotalCount > 0 Then ProgressIncrementHandler(1 / TotalCount)
                     Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
                     Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
-                    If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then
-                        Continue For '不创建空文件夹
-                    Else
-                        Directory.CreateDirectory(GetPathFromFullPath(DestinationPath))
-                        Entry.ExtractToFile(DestinationPath, True)
-                    End If
+                    If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then Continue For '不创建空文件夹
+                    Directory.CreateDirectory(GetPathFromFullPath(DestinationPath))
+                    Entry.ExtractToFile(DestinationPath, True)
                 Next
                 Next
             End Using
             End Using
         End If
         End If
@@ -1328,11 +1315,20 @@ Re:
     ''' </summary>
     ''' </summary>
     Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer
     Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer
         If Not Directory.Exists(Path) Then Return 0
         If Not Directory.Exists(Path) Then Return 0
+        If Not Path.EndsWithF("\") Then Path &= "\"
+        If Path.EndsWith(":\") OrElse
+           Path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\" OrElse
+           Path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) & "\" OrElse
+           Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\" Then '#7030
+            Throw New UnauthorizedAccessException("这是重要的系统文件夹,无法删除:" & Path)
+        End If
+        '实际删除
         Dim DeletedCount As Integer = 0
         Dim DeletedCount As Integer = 0
         Dim Files As String()
         Dim Files As String()
         Try
         Try
             Files = Directory.GetFiles(Path)
             Files = Directory.GetFiles(Path)
         Catch ex As DirectoryNotFoundException '#4549
         Catch ex As DirectoryNotFoundException '#4549
+            If Not Directory.Exists(Path) Then Return 0 '可能已被其他线程删除
             Log(ex, $"疑似为孤立符号链接,尝试直接删除({Path})", LogLevel.Developer)
             Log(ex, $"疑似为孤立符号链接,尝试直接删除({Path})", LogLevel.Developer)
             Directory.Delete(Path)
             Directory.Delete(Path)
             Return 0
             Return 0
@@ -1363,8 +1359,9 @@ RetryFile:
 RetryDir:
 RetryDir:
         Try
         Try
             Directory.Delete(Path, True)
             Directory.Delete(Path, True)
+        Catch ex As DirectoryNotFoundException
         Catch ex As Exception
         Catch ex As Exception
-            If Not RetriedDir Then
+            If Not RetriedDir AndAlso Not RunInUi() Then
                 RetriedDir = True
                 RetriedDir = True
                 Log(ex, $"删除文件夹失败,将在 0.3s 后重试({Path})")
                 Log(ex, $"删除文件夹失败,将在 0.3s 后重试({Path})")
                 Thread.Sleep(300)
                 Thread.Sleep(300)
@@ -1386,17 +1383,17 @@ RetryDir:
         ToPath = ToPath.Replace("/", "\")
         ToPath = ToPath.Replace("/", "\")
         If Not ToPath.EndsWithF("\") Then ToPath &= "\"
         If Not ToPath.EndsWithF("\") Then ToPath &= "\"
         Dim AllFiles = EnumerateFiles(FromPath).ToList
         Dim AllFiles = EnumerateFiles(FromPath).ToList
-        Dim FileCount As Integer = AllFiles.Count
+        Dim FileCount As Long = AllFiles.Count
         For Each File In AllFiles
         For Each File In AllFiles
             CopyFile(File.FullName, File.FullName.Replace(FromPath, ToPath))
             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
         Next
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 遍历文件夹中的所有文件。
     ''' 遍历文件夹中的所有文件。
     ''' </summary>
     ''' </summary>
     Public Function EnumerateFiles(Directory As String) As IEnumerable(Of FileInfo)
     Public Function EnumerateFiles(Directory As String) As IEnumerable(Of FileInfo)
-        Dim Info As New DirectoryInfo(ShortenPath(Directory))
+        Dim Info As New DirectoryInfo(Directory)
         If Not Info.Exists Then Return New List(Of FileInfo)
         If Not Info.Exists Then Return New List(Of FileInfo)
         Return Info.EnumerateFiles("*", SearchOption.AllDirectories)
         Return Info.EnumerateFiles("*", SearchOption.AllDirectories)
     End Function
     End Function
@@ -1404,13 +1401,15 @@ RetryDir:
     ''' <summary>
     ''' <summary>
     ''' 若路径长度大于指定值,则将长路径转换为短路径。
     ''' 若路径长度大于指定值,则将长路径转换为短路径。
     ''' </summary>
     ''' </summary>
-    Public Function ShortenPath(LongPath As String, Optional ShortenThreshold As Integer = 247) As String
-        If LongPath.Length <= ShortenThreshold Then Return LongPath
+    Public Function ShortenPath(LongPath As String) As String
+        If LongPath.Length <= 200 OrElse LongPath.StartsWithF("http", True) Then Return LongPath
+        If Not Directory.Exists(LongPath) AndAlso Not File.Exists(LongPath) Then Return LongPath
         Dim ShortPath As New StringBuilder(260)
         Dim ShortPath As New StringBuilder(260)
         GetShortPathName(LongPath, ShortPath, 260)
         GetShortPathName(LongPath, ShortPath, 260)
-        Return ShortPath.ToString
+        ShortenPath = ShortPath.ToString
+        If String.IsNullOrEmpty(ShortenPath) Then Return LongPath
     End Function
     End Function
-    Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (ByVal lpszLongPath As String, ByVal lpszShortPath As StringBuilder, ByVal cchBuffer As Integer) As Integer
+    Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (lpszLongPath As String, lpszShortPath As StringBuilder, cchBuffer As Integer) As Integer
 
 
 #End Region
 #End Region
 
 
@@ -1419,10 +1418,10 @@ RetryDir:
     Public vbRQ As Char = Convert.ToChar(8221)
     Public vbRQ As Char = Convert.ToChar(8221)
 
 
     ''' <summary>
     ''' <summary>
-    ''' 提取 Exception 的具体描述与堆栈。
+    ''' 提取 Exception 与 Inner Exception 的详细描述与堆栈信息。返回内容总是多于一行。
     ''' </summary>
     ''' </summary>
     ''' <param name="ShowAllStacks">是否必须显示所有堆栈。通常用于判定堆栈信息。</param>
     ''' <param name="ShowAllStacks">是否必须显示所有堆栈。通常用于判定堆栈信息。</param>
-    Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String
+    <Extension> Public Function GetDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String
         If Ex Is Nothing Then Return "无可用错误信息!"
         If Ex Is Nothing Then Return "无可用错误信息!"
 
 
         '获取最底层的异常
         '获取最底层的异常
@@ -1448,32 +1447,18 @@ RetryDir:
             IsInner = True
             IsInner = True
         Loop
         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
-
         '构造输出信息
         '构造输出信息
-        If CommonReason Is Nothing Then
+        Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList)
+        If UsualReason Is Nothing Then
             Return DescList.Join(vbCrLf)
             Return DescList.Join(vbCrLf)
         Else
         Else
-            Return CommonReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf)
+            Return UsualReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf)
         End If
         End If
     End Function
     End Function
     ''' <summary>
     ''' <summary>
-    ''' 提取 Exception 描述,汇总到一行。
+    ''' 提取 Exception 与 Inner Exception 的描述,汇总到一行。
     ''' </summary>
     ''' </summary>
-    Public Function GetExceptionSummary(Ex As Exception) As String
+    <Extension> Public Function GetBrief(Ex As Exception) As String
         If Ex Is Nothing Then Return "无可用错误信息!"
         If Ex Is Nothing Then Return "无可用错误信息!"
 
 
         '获取最底层的异常
         '获取最底层的异常
@@ -1491,27 +1476,30 @@ RetryDir:
         DescList = DescList.Distinct.ToList
         DescList = DescList.Distinct.ToList
         Dim Desc As String = Join(DescList, vbCrLf & "→ ")
         Dim Desc As String = Join(DescList, vbCrLf & "→ ")
 
 
-        '常见错误(记得同时修改上面的)
-        Dim CommonReason As String = Nothing
+        '构造输出信息
+        Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList)
+        If UsualReason IsNot Nothing Then
+            Return UsualReason & "详细错误:" & DescList.First
+        Else
+            DescList.Reverse() '让最深层错误在最左边
+            Return Join(DescList, " ← ")
+        End If
+    End Function
+    Private Function AnalyzeUsualReason(InnerEx As Exception, OuterEx 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
         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,然后再尝试安装。"
+            Return "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。"
         ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
         ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
-            CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
+            Return "PCL 的权限不足。请尝试右键 PCL 选择以管理员身份运行。"
         ElseIf TypeOf InnerEx Is OutOfMemoryException Then
         ElseIf TypeOf InnerEx Is OutOfMemoryException Then
-            CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。"
+            Return "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。"
         ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
         ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
-            CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
-        ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
-                "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) Desc.Contains(s)) Then
-            CommonReason = "你的网络环境不佳,导致难以连接到服务器。请检查网络,多重试几次,或尝试使用 VPN。"
-        End If
-
-        '构造输出信息
-        If CommonReason IsNot Nothing Then
-            Return CommonReason & "详细错误:" & DescList.First
+            Return "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
+        ElseIf TypeOf InnerEx Is SocketException AndAlso DescList.Any(Function(l) l.Contains("WSAStartup")) Then
+            Return "请尝试卸载中国移动云盘,然后再试。"
+        ElseIf OuterEx.IsNetworkRelated() Then
+            Return "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。"
         Else
         Else
-            DescList.Reverse() '让最深层错误在最左边
-            Return Join(DescList, " ← ")
+            Return Nothing
         End If
         End If
     End Function
     End Function
 
 
@@ -1550,12 +1538,12 @@ RetryDir:
     ''' <summary>
     ''' <summary>
     ''' 获取 JSON 对象。
     ''' 获取 JSON 对象。
     ''' </summary>
     ''' </summary>
-    Public Function GetJson(Data As String)
+    Public Function GetJson(Data As String) As JToken
         Try
         Try
             Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local})
             Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local})
         Catch ex As Exception
         Catch ex As Exception
             Dim Length As Integer = If(Data, "").Length
             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 Try
     End Function
     End Function
 
 
@@ -1982,8 +1970,8 @@ RetryDir:
 #Region "系统"
 #Region "系统"
 
 
     ''' <summary>
     ''' <summary>
-    ''' 线程安全的,可以直接使用 For Each 的 List。
-    ''' 在使用 For Each 循环时,列表的结果可能并非最新,但不会抛出异常。
+    ''' 线程安全的 List。
+    ''' 通过在 For Each 循环中使用一个浅表副本规避多线程操作或移除自身导致的异常。
     ''' </summary>
     ''' </summary>
     Public Class SafeList(Of T)
     Public Class SafeList(Of T)
         Inherits SynchronizedCollection(Of T)
         Inherits SynchronizedCollection(Of T)
@@ -2015,8 +2003,8 @@ RetryDir:
     End Class
     End Class
 
 
     ''' <summary>
     ''' <summary>
-    ''' 线程安全的,可以直接使用 For Each 的字典。
-    ''' 在使用 For Each 循环时,字典的结果可能并非最新,但不会抛出异常。
+    ''' 线程安全的字典。
+    ''' 通过在 For Each 循环中使用一个浅表副本规避多线程操作或移除自身导致的异常。
     ''' </summary>
     ''' </summary>
     Public Class SafeDictionary(Of TKey, TValue)
     Public Class SafeDictionary(Of TKey, TValue)
         Implements IDictionary(Of TKey, TValue)
         Implements IDictionary(Of TKey, TValue)
@@ -2028,8 +2016,8 @@ RetryDir:
         '构造函数
         '构造函数
         Public Sub New()
         Public Sub New()
         End Sub
         End Sub
-        Public Sub New(data As IEnumerable(Of KeyValuePair(Of TKey, TValue)))
-            For Each DataItem In data
+        Public Sub New(Data As IEnumerable(Of KeyValuePair(Of TKey, TValue)))
+            For Each DataItem In Data
                 _Dictionary.Add(DataItem.Key, DataItem.Value)
                 _Dictionary.Add(DataItem.Key, DataItem.Value)
             Next
             Next
         End Sub
         End Sub
@@ -2130,6 +2118,19 @@ RetryDir:
         End Function
         End Function
     End Class
     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>
     ''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。
     ''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。
     ''' </summary>
     ''' </summary>
@@ -2172,16 +2173,16 @@ RetryDir:
     ''' 返回程序的返回代码,如果运行失败将抛出异常。
     ''' 返回程序的返回代码,如果运行失败将抛出异常。
     ''' </summary>
     ''' </summary>
     Public Function RunAsAdmin(Argument As String) As Integer
     Public Function RunAsAdmin(Argument As String) As Integer
-        Dim NewProcess = Process.Start(New ProcessStartInfo(PathWithName) With {.Verb = "runas", .Arguments = Argument})
+        Dim NewProcess = StartProcess(New ProcessStartInfo(PathWithName) With {.Verb = "runas", .Arguments = Argument})
         NewProcess.WaitForExit()
         NewProcess.WaitForExit()
         Return NewProcess.ExitCode
         Return NewProcess.ExitCode
     End Function
     End Function
 
 
     ''' <summary>
     ''' <summary>
-    ''' 判断当前系统语言是否为 zh-CN。
+    ''' 判断当前系统语言是否为中文。
     ''' </summary>
     ''' </summary>
     Public Function IsSystemLanguageChinese() As Boolean
     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
     End Function
 
 
     Private Uuid As Integer = 1
     Private Uuid As Integer = 1
@@ -2224,6 +2225,9 @@ NextElement:
         Next i
         Next i
         Return ResultArray
         Return ResultArray
     End Function
     End Function
+    <Extension> Public Function Any(Arr As UIElementCollection) As Boolean
+        Return Arr?.Count > 0
+    End Function
 
 
     ''' <summary>
     ''' <summary>
     ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。
     ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。
@@ -2328,42 +2332,21 @@ NextElement:
     End Class
     End Class
 
 
     ''' <summary>
     ''' <summary>
-    ''' 前台运行文件。
+    ''' 启动进程并返回进程实例,若失败则抛出异常。
+    ''' 会自动进行长路径处理。
     ''' </summary>
     ''' </summary>
-    ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
-    ''' <param name="Arguments">运行参数。</param>
-    Public Sub ShellOnly(FileName As String, Optional Arguments As String = "")
-        FileName = ShortenPath(FileName)
-        Using Program As New Process
-            Program.StartInfo.Arguments = Arguments
-            Program.StartInfo.FileName = FileName
-            Log("[System] 执行外部命令:" & FileName & " " & Arguments)
-            Program.Start()
-        End Using
-    End Sub
+    Public Function StartProcess(FileName As String, Optional Arguments As String = "") As Process
+        Return StartProcess(New ProcessStartInfo With {.FileName = FileName, .Arguments = Arguments})
+    End Function
     ''' <summary>
     ''' <summary>
-    ''' 前台运行文件并返回返回值。
+    ''' 启动进程并返回进程实例,若失败则抛出异常。
+    ''' 会自动进行长路径处理。
     ''' </summary>
     ''' </summary>
-    ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
-    ''' <param name="Arguments">运行参数。</param>
-    ''' <param name="Timeout">等待该程序结束的最长时间(毫秒)。超时会返回 Result.Timeout。</param>
-    Public Function ShellAndGetExitCode(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000) As ProcessReturnValues
-        Try
-            Using Program As New Process
-                Program.StartInfo.Arguments = Arguments
-                Program.StartInfo.FileName = FileName
-                Log("[System] 执行外部命令并等待返回码:" & FileName & " " & Arguments)
-                Program.Start()
-                If Program.WaitForExit(Timeout) Then
-                    Return Program.ExitCode
-                Else
-                    Return ProcessReturnValues.Timeout
-                End If
-            End Using
-        Catch ex As Exception
-            Log(ex, "执行命令失败:" & FileName, LogLevel.Msgbox)
-            Return ProcessReturnValues.Fail
-        End Try
+    Public Function StartProcess(StartInfo As ProcessStartInfo) As Process
+        StartInfo.FileName = ShortenPath(StartInfo.FileName)
+        If Not String.IsNullOrEmpty(StartInfo.WorkingDirectory) Then StartInfo.WorkingDirectory = ShortenPath(StartInfo.WorkingDirectory)
+        Log("[System] 启动进程:" & StartInfo.FileName & " " & StartInfo.Arguments)
+        Return Process.Start(StartInfo)
     End Function
     End Function
     ''' <summary>
     ''' <summary>
     ''' 静默运行文件并返回输出流字符串。执行失败会抛出异常。
     ''' 静默运行文件并返回输出流字符串。执行失败会抛出异常。
@@ -2371,10 +2354,11 @@ NextElement:
     ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
     ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
     ''' <param name="Arguments">运行参数。</param>
     ''' <param name="Arguments">运行参数。</param>
     ''' <param name="Timeout">等待该程序结束的最长时间(毫秒)。超时会抛出错误。</param>
     ''' <param name="Timeout">等待该程序结束的最长时间(毫秒)。超时会抛出错误。</param>
-    Public Function ShellAndGetOutput(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000, Optional WorkingDirectory As String = Nothing) As String
+    Public Function StartProcessAndGetOutput(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000,
+                                      Optional WorkingDirectory As String = Nothing) As String
         Dim Info = New ProcessStartInfo With {
         Dim Info = New ProcessStartInfo With {
             .Arguments = Arguments,
             .Arguments = Arguments,
-            .FileName = FileName,
+            .FileName = ShortenPath(FileName),
             .UseShellExecute = False,
             .UseShellExecute = False,
             .CreateNoWindow = True,
             .CreateNoWindow = True,
             .RedirectStandardError = True,
             .RedirectStandardError = True,
@@ -2389,7 +2373,7 @@ NextElement:
             End If
             End If
         End If
         End If
         Log("[System] 执行外部命令并等待返回结果:" & FileName & " " & Arguments)
         Log("[System] 执行外部命令并等待返回结果:" & FileName & " " & Arguments)
-        Using Program As New Process() With {.StartInfo = Info}
+        Using Program As New Process With {.StartInfo = Info}
             Program.Start()
             Program.Start()
             Dim Result As String = Program.StandardOutput.ReadToEnd & Program.StandardError.ReadToEnd
             Dim Result As String = Program.StandardOutput.ReadToEnd & Program.StandardError.ReadToEnd
             Program.WaitForExit(Timeout)
             Program.WaitForExit(Timeout)
@@ -2457,7 +2441,7 @@ NextElement:
     ''' </summary>
     ''' </summary>
     Public Sub RunInThread(Action As Action)
     Public Sub RunInThread(Action As Action)
         If RunInUi() Then
         If RunInUi() Then
-            RunInNewThread(Action, "Runtime Invoke " & GetUuid() & "#")
+            RunInNewThread(Action, "Invoke " & GetUuid())
         Else
         Else
             Action()
             Action()
         End If
         End If
@@ -2491,17 +2475,14 @@ NextElement:
     ''' <summary>
     ''' <summary>
     ''' 尝试从字典中获取某项,如果该项不存在,则返回默认值。
     ''' 尝试从字典中获取某项,如果该项不存在,则返回默认值。
     ''' </summary>
     ''' </summary>
-    <Extension> Public Function GetOrDefault(Of TKey, TValue)(Dict As Dictionary(Of TKey, TValue), Key As TKey, Optional DefaultValue As TValue = Nothing) As TValue
-        If Dict.ContainsKey(Key) Then
-            Return Dict(Key)
-        Else
-            Return DefaultValue
-        End If
+    <Extension> Public Function GetOrDefault(Of TKey, TValue)(Dict As IDictionary(Of TKey, TValue), Key As TKey, Optional DefaultValue As TValue = Nothing) As TValue
+        Dim Result As TValue
+        Return If(Dict.TryGetValue(Key, Result), Result, DefaultValue)
     End Function
     End Function
     ''' <summary>
     ''' <summary>
     ''' 将某项添加到以列表作为值的字典中。
     ''' 将某项添加到以列表作为值的字典中。
     ''' </summary>
     ''' </summary>
-    <Extension> Public Sub AddToList(Of TKey, TValue)(Dict As Dictionary(Of TKey, List(Of TValue)), Key As TKey, Value As TValue)
+    <Extension> Public Sub AddToList(Of TKey, TValue)(Dict As IDictionary(Of TKey, List(Of TValue)), Key As TKey, Value As TValue)
         If Dict.ContainsKey(Key) Then
         If Dict.ContainsKey(Key) Then
             Dict(Key).Add(Value)
             Dict(Key).Add(Value)
         Else
         Else
@@ -2549,7 +2530,7 @@ NextElement:
                 Throw New Exception(Url & " 不是一个有效的网址,它必须以 http 开头!")
                 Throw New Exception(Url & " 不是一个有效的网址,它必须以 http 开头!")
             End If
             End If
             Log("[System] 正在打开网页:" & Url)
             Log("[System] 正在打开网页:" & Url)
-            Process.Start(Url)
+            StartProcess(Url)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "无法打开网页(" & Url & ")")
             Log(ex, "无法打开网页(" & Url & ")")
             ClipboardSet(Url, False)
             ClipboardSet(Url, False)
@@ -2563,12 +2544,12 @@ NextElement:
     ''' </summary>
     ''' </summary>
     Public Sub OpenExplorer(Location As String)
     Public Sub OpenExplorer(Location As String)
         Try
         Try
-            Location = ShortenPath(Location.Replace("/", "\").Trim(" "c, """"c))
+            Location = Location.Replace("/", "\").Trim(" "c, """"c)
             Log("[System] 正在打开资源管理器:" & Location)
             Log("[System] 正在打开资源管理器:" & Location)
             If Location.EndsWith("\") Then
             If Location.EndsWith("\") Then
-                ShellOnly(Location)
+                StartProcess(Location)
             Else
             Else
-                ShellOnly("explorer", $"/select,""{Location}""")
+                StartProcess("explorer", $"/select,""{Location}""")
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "打开资源管理器失败,请尝试关闭安全软件(如 360 安全卫士)", LogLevel.Msgbox)
             Log(ex, "打开资源管理器失败,请尝试关闭安全软件(如 360 安全卫士)", LogLevel.Msgbox)
@@ -2620,10 +2601,6 @@ Retry:
     ''' 相对增减控件的左边距。
     ''' 相对增减控件的左边距。
     ''' </summary>
     ''' </summary>
     Public Sub DeltaLeft(control As FrameworkElement, newValue As Double)
     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
         If TypeOf control Is Window Then
             '窗口改变
             '窗口改变
             CType(control, Window).Left += newValue
             CType(control, Window).Left += newValue
@@ -2635,8 +2612,6 @@ Retry:
                 Case HorizontalAlignment.Right
                 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, 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)
                     '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 Select
         End If
         End If
     End Sub
     End Sub
@@ -2644,17 +2619,12 @@ Retry:
     ''' 设置控件的左边距。(仅针对置左控件)
     ''' 设置控件的左边距。(仅针对置左控件)
     ''' </summary>
     ''' </summary>
     Public Sub SetLeft(control As FrameworkElement, newValue As Double)
     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)
         control.Margin = New Thickness(newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom)
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 相对增减控件的上边距。
     ''' 相对增减控件的上边距。
     ''' </summary>
     ''' </summary>
     Public Sub DeltaTop(control As FrameworkElement, newValue As Double)
     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
         If TypeOf control Is Window Then
             '窗口改变
             '窗口改变
             CType(control, Window).Top += newValue
             CType(control, Window).Top += newValue
@@ -2666,12 +2636,9 @@ Retry:
                 Case VerticalAlignment.Bottom
                 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, 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)
                     '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 Select
         End If
         End If
-
-        'If Double.IsNaN(newValue) OrElse Double.IsInfinity(newValue) Then Exit Sub '安全性检查
+        'If Double.IsNaN(newValue) OrElse Double.IsInfinity(newValue) Then Return '安全性检查
         'Select Case control.VerticalAlignment
         'Select Case control.VerticalAlignment
         '  Case VerticalAlignment.Top, VerticalAlignment.Stretch, VerticalAlignment.Center
         '  Case VerticalAlignment.Top, VerticalAlignment.Stretch, VerticalAlignment.Center
         '      control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom)
         '      control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom)
@@ -2684,7 +2651,6 @@ Retry:
     ''' 设置控件的顶边距。(仅针对置上控件)
     ''' 设置控件的顶边距。(仅针对置上控件)
     ''' </summary>
     ''' </summary>
     Public Sub SetTop(control As FrameworkElement, newValue As Double)
     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)
         control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom)
     End Sub
     End Sub
 
 
@@ -2755,11 +2721,11 @@ Retry:
             Using Reader As New XamlXmlReader(Stream)
             Using Reader As New XamlXmlReader(Stream)
                 While Reader.Read()
                 While Reader.Read()
                     For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider)}
                     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
                     Next
                     For Each BlackListMember In {"Code", "FactoryMethod", "Static"}
                     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
                     Next
                 End While
                 End While
             End Using
             End Using
@@ -2844,9 +2810,10 @@ Retry:
         ''' </summary>
         ''' </summary>
         Feedback = 5
         Feedback = 5
         ''' <summary>
         ''' <summary>
-        ''' 弹窗,结束程序。
+        ''' 弹出 Windows 原生弹窗,要求反馈。在无法保证 WPF 窗口能正常运行时使用此级别。
+        ''' 在第二次触发后会直接结束程序。
         ''' </summary>
         ''' </summary>
-        Assert = 6
+        Critical = 6
     End Enum
     End Enum
     Private LogList As New StringBuilder
     Private LogList As New StringBuilder
     Private LogWritter As StreamWriter
     Private LogWritter As StreamWriter
@@ -2889,7 +2856,7 @@ Retry:
     Private ReadOnly LogFlushLock As New Object '防止外部调用 LogFlush 时同时输出多次日志
     Private ReadOnly LogFlushLock As New Object '防止外部调用 LogFlush 时同时输出多次日志
     Public Sub LogFlush()
     Public Sub LogFlush()
         On Error Resume Next
         On Error Resume Next
-        If LogWritter Is Nothing Then Exit Sub
+        If LogWritter Is Nothing Then Return
         Dim Log As String = Nothing
         Dim Log As String = Nothing
         SyncLock LogFlushLock
         SyncLock LogFlushLock
             If LogList.Length > 0 Then
             If LogList.Length > 0 Then
@@ -2905,6 +2872,7 @@ Retry:
     End Sub
     End Sub
 
 
     Private ReadOnly LogListLock As New Object '防止日志乱码,只在调试模式下启用
     Private ReadOnly LogListLock As New Object '防止日志乱码,只在调试模式下启用
+    Private IsCriticalErrorTriggered As Boolean = False
     ''' <summary>
     ''' <summary>
     ''' 输出 Log。
     ''' 输出 Log。
     ''' </summary>
     ''' </summary>
@@ -2915,7 +2883,7 @@ Retry:
         '处理错误会导致再次调用 Log() 导致无限循环
         '处理错误会导致再次调用 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
         If ModeDebug Then
             SyncLock LogListLock
             SyncLock LogListLock
                 LogList.Append(AppendText)
                 LogList.Append(AppendText)
@@ -2926,7 +2894,7 @@ Retry:
 #If DEBUG Then
 #If DEBUG Then
         Console.Write(AppendText)
         Console.Write(AppendText)
 #End If
 #End If
-        If IsProgramEnded OrElse Level = LogLevel.Normal Then Exit Sub
+        If IsProgramEnded OrElse Level = LogLevel.Normal Then Return
 
 
         '去除前缀
         '去除前缀
         Text = Text.RegexReplace("\[[^\]]+?\] ", "")
         Text = Text.RegexReplace("\[[^\]]+?\] ", "")
@@ -2953,22 +2921,26 @@ Retry:
                 Else
                 Else
                     MyMsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True)
                     MyMsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True)
                 End If
                 End If
-            Case LogLevel.Assert
-                Dim Time As Long = GetTimeTick()
+            Case LogLevel.Critical
+                If IsCriticalErrorTriggered Then
+                    FormMain.EndProgramForce(ProcessReturnValues.Exception)
+                    Return
+                End If
+                IsCriticalErrorTriggered = True
                 If CanFeedback(False) Then
                 If CanFeedback(False) Then
                     If MsgBox(Text & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True)
                     If MsgBox(Text & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True)
                 Else
                 Else
                     MsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title)
                     MsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title)
                 End If
                 End If
-                If GetTimeTick() - Time < 1500 Then
-                    '弹窗无法保留
-                    Log("[System] PCL 已崩溃:" & vbCrLf & Text)
-                    FormMain.EndProgramForce(ProcessReturnValues.Exception)
-                Else
-                    FormMain.EndProgramForce(ProcessReturnValues.Fail)
-                End If
         End Select
         End Select
 
 
+        '上报
+        If Level >= LogLevel.Hint Then
+            Telemetry("PCL 错误",
+                      "Level", GetStringFromEnum(Level),
+                      "Title", FilterUserName(FilterAccessToken(Title, "*"), "*"),
+                      "Exception", FilterUserName(FilterAccessToken(Text, "*"), "*"))
+        End If
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 输出错误信息。
     ''' 输出错误信息。
@@ -2976,13 +2948,13 @@ Retry:
     ''' <param name="Desc">错误描述。会在处理时在末尾加入冒号。</param>
     ''' <param name="Desc">错误描述。会在处理时在末尾加入冒号。</param>
     Public Sub Log(Ex As Exception, Desc As String, Optional Level As LogLevel = LogLevel.Debug, Optional Title As String = "出现错误")
     Public Sub Log(Ex As Exception, Desc As String, Optional Level As LogLevel = LogLevel.Debug, Optional Title As String = "出现错误")
         On Error Resume Next
         On Error Resume Next
-        If TypeOf Ex Is ThreadInterruptedException Then Exit Sub
+        If TypeOf Ex Is ThreadInterruptedException Then Return
 
 
         '获取错误信息
         '获取错误信息
-        Dim ExFull As String = Desc & ":" & GetExceptionDetail(Ex)
+        Dim ExFull As String = $"{Desc}:{Ex.GetDetail()}"
 
 
         '输出日志
         '输出日志
-        Dim AppendText As String = "[" & GetTimeNow() & "] " & Desc & ":" & GetExceptionDetail(Ex, True) & vbCrLf '减轻同步锁占用
+        Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{Ex.GetDetail(True)}{vbCrLf}" '减轻同步锁占用
         If ModeDebug Then
         If ModeDebug Then
             SyncLock LogListLock
             SyncLock LogListLock
                 LogList.Append(AppendText)
                 LogList.Append(AppendText)
@@ -2993,26 +2965,26 @@ Retry:
 #If DEBUG Then
 #If DEBUG Then
         Console.Write(AppendText)
         Console.Write(AppendText)
 #End If
 #End If
-        If IsProgramEnded Then Exit Sub
+        If IsProgramEnded Then Return
 
 
         '输出提示
         '输出提示
         Select Case Level
         Select Case Level
             Case LogLevel.Normal
             Case LogLevel.Normal
 #If DEBUG Then
 #If DEBUG Then
             Case LogLevel.Developer
             Case LogLevel.Developer
-                Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
+                Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Hint("[开发者模式] " & ExLine, HintType.Info, False)
                 Hint("[开发者模式] " & ExLine, HintType.Info, False)
             Case LogLevel.Debug
             Case LogLevel.Debug
-                Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
+                Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Hint("[调试模式] " & ExLine, HintType.Info, False)
                 Hint("[调试模式] " & ExLine, HintType.Info, False)
 #Else
 #Else
             Case LogLevel.Developer
             Case LogLevel.Developer
             Case LogLevel.Debug
             Case LogLevel.Debug
-                Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
+                Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False)
                 If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False)
 #End If
 #End If
             Case LogLevel.Hint
             Case LogLevel.Hint
-                Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
+                Dim ExLine As String = Desc & ":" & Ex.GetBrief()
                 Hint(ExLine, HintType.Critical, False)
                 Hint(ExLine, HintType.Critical, False)
             Case LogLevel.Msgbox
             Case LogLevel.Msgbox
                 MyMsgBox(ExFull, Title, IsWarn:=True)
                 MyMsgBox(ExFull, Title, IsWarn:=True)
@@ -3022,22 +2994,26 @@ Retry:
                 Else
                 Else
                     MyMsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True)
                     MyMsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True)
                 End If
                 End If
-            Case LogLevel.Assert
-                Dim Time As Long = GetTimeTick()
+            Case LogLevel.Critical
+                If IsCriticalErrorTriggered Then
+                    FormMain.EndProgramForce(ProcessReturnValues.Exception)
+                    Return
+                End If
+                IsCriticalErrorTriggered = True
                 If CanFeedback(False) Then
                 If CanFeedback(False) Then
                     If MsgBox(ExFull & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True)
                     If MsgBox(ExFull & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True)
                 Else
                 Else
                     MsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title)
                     MsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title)
                 End If
                 End If
-                If GetTimeTick() - Time < 1500 Then
-                    '弹窗无法保留
-                    Log("[System] PCL 已崩溃:" & vbCrLf & ExFull)
-                    FormMain.EndProgramForce(ProcessReturnValues.Exception)
-                Else
-                    FormMain.EndProgramForce(ProcessReturnValues.Fail)
-                End If
         End Select
         End Select
 
 
+        '上报
+        If Level >= LogLevel.Hint Then
+            Telemetry("PCL 异常",
+                      "Level", GetStringFromEnum(Level),
+                      "Title", FilterUserName(FilterAccessToken(Title, "*"), "*"),
+                      "Exception", FilterUserName(FilterAccessToken(ExFull, "*"), "*"))
+        End If
     End Sub
     End Sub
 
 
     '反馈
     '反馈
@@ -3047,7 +3023,7 @@ Retry:
         If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
         If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
             OpenExplorer(Path & "PCL\Log1.txt")
             OpenExplorer(Path & "PCL\Log1.txt")
         End If
         End If
-        OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/")
+        OpenWebsite("https://github.com/Meloong-Git/PCL/issues/")
     End Sub
     End Sub
     Public Function CanFeedback(ShowHint As Boolean) As Boolean
     Public Function CanFeedback(ShowHint As Boolean) As Boolean
         If False.Equals(PageSetupSystem.IsLauncherNewest) Then
         If False.Equals(PageSetupSystem.IsLauncherNewest) Then
@@ -3074,9 +3050,23 @@ Retry:
             "文件位置:" & Path)
             "文件位置:" & Path)
     End Sub
     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 ex As Exception
+                Log(ex, "匿名数据上报失败")
+            End Try
+        End Sub, "Telemetry", ThreadPriority.Lowest)
     End Sub
     End Sub
 
 
     '获取当前的堆栈信息
     '获取当前的堆栈信息

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

@@ -85,7 +85,7 @@
                 Return _State
                 Return _State
             End Get
             End Get
             Set(value As LoadState)
             Set(value As LoadState)
-                If _State = value Then Exit Property
+                If _State = value Then Return
                 Dim OldState = _State
                 Dim OldState = _State
                 If value = LoadState.Finished AndAlso Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
                 If value = LoadState.Finished AndAlso Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
                 _State = value
                 _State = value
@@ -112,7 +112,7 @@
                 Return _LoadingState
                 Return _LoadingState
             End Get
             End Get
             Set(value As MyLoading.MyLoadingState)
             Set(value As MyLoading.MyLoadingState)
-                If _LoadingState = value Then Exit Property
+                If _LoadingState = value Then Return
                 Dim OldState = _LoadingState
                 Dim OldState = _LoadingState
                 _LoadingState = value
                 _LoadingState = value
                 RaiseEvent LoadingStateChanged(value, OldState)
                 RaiseEvent LoadingStateChanged(value, OldState)
@@ -154,7 +154,7 @@
                 End Select
                 End Select
             End Get
             End Get
             Set(value As Double)
             Set(value As Double)
-                If _Progress = value Then Exit Property
+                If _Progress = value Then Return
                 Dim OldValue = _Progress
                 Dim OldValue = _Progress
                 _Progress = value
                 _Progress = value
                 RaiseEvent ProgressChanged(value, OldValue)
                 RaiseEvent ProgressChanged(value, OldValue)
@@ -184,7 +184,7 @@
                 Thread.Sleep(10)
                 Thread.Sleep(10)
             Loop
             Loop
             If State = LoadState.Finished Then
             If State = LoadState.Finished Then
-                Exit Sub
+                Return
             ElseIf State = LoadState.Aborted Then
             ElseIf State = LoadState.Aborted Then
                 Throw New ThreadInterruptedException("加载器执行已中断。")
                 Throw New ThreadInterruptedException("加载器执行已中断。")
             ElseIf IsNothing([Error]) Then
             ElseIf IsNothing([Error]) Then
@@ -207,7 +207,7 @@
                 If Timeout < 0 Then Throw New TimeoutException(TimeoutMessage)
                 If Timeout < 0 Then Throw New TimeoutException(TimeoutMessage)
             Loop
             Loop
             If State = LoadState.Finished Then
             If State = LoadState.Finished Then
-                Exit Sub
+                Return
             ElseIf State = LoadState.Aborted Then
             ElseIf State = LoadState.Aborted Then
                 Throw New ThreadInterruptedException("加载器执行已中断。")
                 Throw New ThreadInterruptedException("加载器执行已中断。")
             ElseIf IsNothing([Error]) Then
             ElseIf IsNothing([Error]) Then
@@ -290,11 +290,12 @@
             '检验输入以确定情况
             '检验输入以确定情况
             If IsForceRestart Then Return True '强制要求重启
             If IsForceRestart Then Return True '强制要求重启
             If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同
             If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同
-            If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束
-               (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '没有超时
-                Return False '则不重试
+            If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束…
+               (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse
+               LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '…且没有超时…
+                Return False '…则不重试
             Else
             Else
-                Return True '需要开始
+                Return True '否则需要重启
             End If
             End If
         End Function
         End Function
         Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
         Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
@@ -308,48 +309,49 @@
                     Progress = -1
                     Progress = -1
                 End SyncLock
                 End SyncLock
             Else
             Else
-                Exit Sub
+                Return
             End If
             End If
 
 
             LastRunningThread = New Thread(
             LastRunningThread = New Thread(
-                Sub()
-                    Try
-                        IsForceRestarting = IsForceRestart
-                        If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动")
-                        LoadDelegate(Me)
-                        If IsAborted Then
-                            Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})", LogLevel.Developer)
-                        Else
-                            RaisePreviewFinish()
-                            State = LoadState.Finished
-                            LastFinishedTime = GetTimeTick() '未中断,本次输出有效
-                        End If
-                    Catch ex As CancelledException
-                        If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%")
-                        If Not IsAborted Then State = LoadState.Aborted
-                    Catch ex As ThreadInterruptedException
-                        If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发线程中断,已完成 {Math.Round(Progress * 100)}%")
-                        '如果线程是因为判断到 IsAborted 而提前中止,则代表已有新线程被重启,此时不应当改为 Aborted
-                        '如果线程是在没有 IsAborted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Aborted
-                        If Not IsAborted Then State = LoadState.Aborted
-                    Catch ex As Exception
-                        If IsAborted Then Exit Sub
-                        Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%", LogLevel.Developer)
-                        [Error] = ex
-                        State = LoadState.Failed
-                    End Try
-                End Sub) With {.Name = Name, .Priority = ThreadPriority}
+            Sub()
+                Try
+                    IsForceRestarting = IsForceRestart
+                    If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动")
+                    LoadDelegate(Me)
+                    If IsAborted Then
+                        Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})", LogLevel.Developer)
+                        Return
+                    End If
+                    If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已完成")
+                    RaisePreviewFinish()
+                    State = LoadState.Finished
+                    LastFinishedTime = GetTimeTick() '未中断,本次输出有效
+                Catch ex As CancelledException
+                    If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%")
+                    If Not IsAborted Then State = LoadState.Aborted
+                Catch ex As ThreadInterruptedException
+                    If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发线程中断,已完成 {Math.Round(Progress * 100)}%")
+                    '如果线程是因为判断到 IsAborted 而提前中止,则代表已有新线程被重启,此时不应当改为 Aborted
+                    '如果线程是在没有 IsAborted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Aborted
+                    If Not IsAborted Then State = LoadState.Aborted
+                Catch ex As Exception
+                    If IsAborted Then Return
+                    Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%", LogLevel.Developer)
+                    [Error] = ex
+                    State = LoadState.Failed
+                End Try
+            End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
             LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
             LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
         End Sub
         End Sub
         Public Overrides Sub Abort()
         Public Overrides Sub Abort()
-            If State <> LoadState.Loading Then Exit Sub
+            If State <> LoadState.Loading Then Return
             SyncLock LockState
             SyncLock LockState
                 State = LoadState.Aborted
                 State = LoadState.Aborted
             End SyncLock
             End SyncLock
             TriggerThreadAbort()
             TriggerThreadAbort()
         End Sub
         End Sub
         Private Sub TriggerThreadAbort()
         Private Sub TriggerThreadAbort()
-            If LastRunningThread Is Nothing Then Exit Sub
+            If LastRunningThread Is Nothing Then Return
             If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({LastRunningThread.ManagedThreadId}) 已中断")
             If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({LastRunningThread.ManagedThreadId}) 已中断")
             If LastRunningThread.IsAlive Then LastRunningThread.Interrupt()
             If LastRunningThread.IsAlive Then LastRunningThread.Interrupt()
             LastRunningThread = Nothing
             LastRunningThread = Nothing
@@ -419,7 +421,7 @@
             '改变状态
             '改变状态
             SyncLock LockState
             SyncLock LockState
                 If State = LoadState.Loading Then
                 If State = LoadState.Loading Then
-                    Exit Sub
+                    Return
                 Else
                 Else
                     State = LoadState.Loading
                     State = LoadState.Loading
                 End If
                 End If
@@ -439,7 +441,7 @@
                 If State = LoadState.Loading OrElse State = LoadState.Waiting Then
                 If State = LoadState.Loading OrElse State = LoadState.Waiting Then
                     State = LoadState.Aborted
                     State = LoadState.Aborted
                 Else
                 Else
-                    Exit Sub
+                    Return
                 End If
                 End If
             End SyncLock
             End SyncLock
             RunInThread(
             RunInThread(
@@ -470,7 +472,7 @@
                 Case Else
                 Case Else
                     '完蛋,出错了
                     '完蛋,出错了
                     SyncLock LockState
                     SyncLock LockState
-                        If State >= LoadState.Finished Then Exit Sub
+                        If State >= LoadState.Finished Then Return
                         [Error] = New Exception(Loader.Name & "失败", Loader.Error)
                         [Error] = New Exception(Loader.Name & "失败", Loader.Error)
                         State = Loader.State
                         State = Loader.State
                     End SyncLock
                     End SyncLock
@@ -478,14 +480,14 @@
                         Loader.Abort()
                         Loader.Abort()
                     Next
                     Next
                     FrmMain.BtnExtraDownload.ShowRefresh()
                     FrmMain.BtnExtraDownload.ShowRefresh()
-                    Exit Sub
+                    Return
             End Select
             End Select
         End Sub
         End Sub
         ''' <summary>
         ''' <summary>
         ''' 触发一次更新,以启动新加载器或完成。
         ''' 触发一次更新,以启动新加载器或完成。
         ''' </summary>
         ''' </summary>
         Private Sub Update()
         Private Sub Update()
-            If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Exit Sub
+            If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Return
             Dim IsFinished As Boolean = True
             Dim IsFinished As Boolean = True
             Dim Blocked As Boolean = False
             Dim Blocked As Boolean = False
             Dim Input As Object = Me.Input
             Dim Input As Object = Me.Input
@@ -584,16 +586,11 @@ Restart:
         Try
         Try
             Dim NewState As Shell.TaskbarItemProgressState
             Dim NewState As Shell.TaskbarItemProgressState
             Dim NewProgress As Double = LoaderTaskbarProgressGet()
             Dim NewProgress As Double = LoaderTaskbarProgressGet()
-            '检查任务是否完成,若完成则移除
-            '外显任务是否已经全部完成
-            Dim IsAllDownloadTaskCompleted As Boolean = True
-            For Each Loader In LoaderTaskbar.ToList()
-                If Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False
-            Next
-            '若单个任务已中止或全部任务已完成,则刷新并移除
-            For Each Task In LoaderTaskbar.ToList()
-                If IsAllDownloadTaskCompleted OrElse Task.State = LoadState.Aborted OrElse Task.State = LoadState.Waiting Then
-                    If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(Task)
+            '若单个任务已中止,或全部任务已完成,则刷新并移除
+            For Each Task In LoaderTaskbar
+                If LoaderTaskbar.All(Function(l) l.State <> LoadState.Loading) OrElse
+                   (Task.State = LoadState.Waiting OrElse Task.State = LoadState.Aborted) Then
+                    FrmSpeedLeft?.TaskRefresh(Task)
                     LoaderTaskbar.Remove(Task)
                     LoaderTaskbar.Remove(Task)
                     Log($"[Taskbar] {Task.Name} 已移出任务列表")
                     Log($"[Taskbar] {Task.Name} 已移出任务列表")
                 End If
                 End If
@@ -626,7 +623,7 @@ Restart:
     Public Function LoaderTaskbarProgressGet() As Double
     Public Function LoaderTaskbarProgressGet() As Double
         Try
         Try
             If Not LoaderTaskbar.Any Then Return 1
             If Not LoaderTaskbar.Any Then Return 1
-            Return MathClamp(LoaderTaskbar.Select(Function(e) e.Progress).Average(), 0, 1)
+            Return MathClamp(LoaderTaskbar.Select(Function(l) l.Progress).Average(), 0, 1)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "获取任务栏进度出错", LogLevel.Feedback)
             Log(ex, "获取任务栏进度出错", LogLevel.Feedback)
             Return 0.5
             Return 0.5

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


+ 34 - 28
Plain Craft Launcher 2/Modules/Base/ModValidate.vb

@@ -9,6 +9,7 @@ Public Module ModValidate
         Dim Result As String = ""
         Dim Result As String = ""
         For Each ValidateRule As Validate In ValidateRules
         For Each ValidateRule As Validate In ValidateRules
             Result = ValidateRule.Validate(Text)
             Result = ValidateRule.Validate(Text)
+            If Result Is Nothing Then Return ""
             If Result <> "" Then Return Result
             If Result <> "" Then Return Result
         Next
         Next
         Return Result
         Return Result
@@ -21,7 +22,7 @@ End Module
 ''' </summary>
 ''' </summary>
 Public MustInherit Class Validate
 Public MustInherit Class Validate
     ''' <summary>
     ''' <summary>
-    ''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因。
+    ''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因;若需要中断检查并直接通过,返回 Nothing。
     ''' </summary>
     ''' </summary>
     Public MustOverride Function Validate(Str As String) As String
     Public MustOverride Function Validate(Str As String) As String
 End Class
 End Class
@@ -135,9 +136,9 @@ Public Class ValidateLength
         Me.Max = Max
         Me.Max = Max
     End Sub
     End Sub
     Public Overrides Function Validate(Str As String) As String
     Public Overrides Function Validate(Str As String) As String
-        If Len(Str) <> Max AndAlso Max = Min Then Return "长度必须为 " & Max & " 个字符!"
-        If Len(Str) > Max Then Return "长度最长为 " & Max & " 个字符!"
-        If Len(Str) < Min Then Return "长度至少需 " & Min & " 个字符!"
+        If Len(Str) <> Max AndAlso Max = Min Then Return $"长度必须为 {Max} 个字符!"
+        If Len(Str) > Max Then Return $"长度最长为 {Max} 个字符!"
+        If Len(Str) < Min Then Return $"长度至少需 {Min} 个字符!"
         Return ""
         Return ""
     End Function
     End Function
 End Class
 End Class
@@ -147,7 +148,7 @@ End Class
 ''' </summary>
 ''' </summary>
 Public Class ValidateExcept
 Public Class ValidateExcept
     Inherits Validate
     Inherits Validate
-    Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
+    Public Property Excepts As New ObjectModel.Collection(Of String)
     Public Property ErrorMessage As String
     Public Property ErrorMessage As String
     Public Sub New()
     Public Sub New()
         ErrorMessage = "输入内容不能包含 %"
         ErrorMessage = "输入内容不能包含 %"
@@ -156,12 +157,15 @@ Public Class ValidateExcept
         Me.Excepts = Excepts
         Me.Excepts = Excepts
         Me.ErrorMessage = ErrorMessage
         Me.ErrorMessage = ErrorMessage
     End Sub
     End Sub
-    Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能包含 %")
-        Me.Excepts = New ObjectModel.Collection(Of String)
-        Me.ErrorMessage = ErrorMessage
-        For Each Data As String In Excepts
-            Me.Excepts.Add(Data)
-        Next
+    Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能包含 %")
+        Me.New(
+            New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)),
+            ErrorMessage)
+    End Sub
+    Public Sub New(Excepts As IEnumerable(Of Char), Optional ErrorMessage As String = "输入内容不能包含 %")
+        Me.New(
+            New ObjectModel.Collection(Of String)(Excepts.Select(Function(c) c.ToString).ToList),
+            ErrorMessage)
     End Sub
     End Sub
     Public Overrides Function Validate(Str As String) As String
     Public Overrides Function Validate(Str As String) As String
         For Each Ch As String In Excepts
         For Each Ch As String In Excepts
@@ -180,7 +184,7 @@ End Class
 ''' </summary>
 ''' </summary>
 Public Class ValidateExceptSame
 Public Class ValidateExceptSame
     Inherits Validate
     Inherits Validate
-    Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
+    Public Property Excepts As New ObjectModel.Collection(Of String)
     Public Property ErrorMessage As String
     Public Property ErrorMessage As String
     Public Property IgnoreCase As Boolean = False
     Public Property IgnoreCase As Boolean = False
     Public Sub New()
     Public Sub New()
@@ -190,13 +194,15 @@ Public Class ValidateExceptSame
         Me.ErrorMessage = ErrorMessage
         Me.ErrorMessage = ErrorMessage
         Me.IgnoreCase = IgnoreCase
         Me.IgnoreCase = IgnoreCase
     End Sub
     End Sub
-    Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
-        Me.Excepts = New ObjectModel.Collection(Of String)
-        For Each Data As String In Excepts
-            Me.Excepts.Add(Data)
-        Next
-        Me.ErrorMessage = ErrorMessage
-        Me.IgnoreCase = IgnoreCase
+    Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
+        Me.New(
+            New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)),
+            ErrorMessage, IgnoreCase)
+    End Sub
+    Public Sub New(Except As String, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
+        Me.New(
+            New ObjectModel.Collection(Of String) From {Except},
+            ErrorMessage, IgnoreCase)
     End Sub
     End Sub
     Public Overrides Function Validate(Str As String) As String
     Public Overrides Function Validate(Str As String) As String
         If Str Is Nothing Then Return ErrorMessage.Replace("%", "null")
         If Str Is Nothing Then Return ErrorMessage.Replace("%", "null")
@@ -234,21 +240,21 @@ Public Class ValidateFolderName
         Try
         Try
             '检查是否为空
             '检查是否为空
             Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
             Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
-            If Not LengthCheck = "" Then Return LengthCheck
+            If LengthCheck <> "" Then Return LengthCheck
             '检查空格
             '检查空格
             If Str.StartsWithF(" ") Then Return "文件夹名不能以空格开头!"
             If Str.StartsWithF(" ") Then Return "文件夹名不能以空格开头!"
             If Str.EndsWithF(" ") Then Return "文件夹名不能以空格结尾!"
             If Str.EndsWithF(" ") Then Return "文件夹名不能以空格结尾!"
             '检查长度
             '检查长度
             LengthCheck = New ValidateLength(1, 100).Validate(Str)
             LengthCheck = New ValidateLength(1, 100).Validate(Str)
-            If Not LengthCheck = "" Then Return LengthCheck
+            If LengthCheck <> "" Then Return LengthCheck
             '检查尾部小数点
             '检查尾部小数点
             If Str.EndsWithF(".") Then Return "文件夹名不能以小数点结尾!"
             If Str.EndsWithF(".") Then Return "文件夹名不能以小数点结尾!"
             '检查特殊字符
             '检查特殊字符
             Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str)
             Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str)
-            If Not CharactCheck = "" Then Return CharactCheck
+            If CharactCheck <> "" Then Return CharactCheck
             '检查特殊字符串
             '检查特殊字符串
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str)
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str)
-            If Not InvalidStrCheck = "" Then Return InvalidStrCheck
+            If InvalidStrCheck <> "" Then Return InvalidStrCheck
             '检查 NTFS 8.3 文件名(#4505)
             '检查 NTFS 8.3 文件名(#4505)
             If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
             If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
             '检查文件夹重名
             '检查文件夹重名
@@ -295,15 +301,15 @@ Public Class ValidateFileName
             If Str.EndsWithF(" ") Then Return "文件名不能以空格结尾!"
             If Str.EndsWithF(" ") Then Return "文件名不能以空格结尾!"
             '检查长度
             '检查长度
             LengthCheck = New ValidateLength(1, 253).Validate(Str & If(ParentFolder, ""))
             LengthCheck = New ValidateLength(1, 253).Validate(Str & If(ParentFolder, ""))
-            If Not LengthCheck = "" Then Return LengthCheck
+            If LengthCheck <> "" Then Return LengthCheck
             '检查尾部小数点
             '检查尾部小数点
             If Str.EndsWithF(".") Then Return "文件名不能以小数点结尾!"
             If Str.EndsWithF(".") Then Return "文件名不能以小数点结尾!"
             '检查特殊字符
             '检查特殊字符
             Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件名不可包含 % 字符!").Validate(Str)
             Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件名不可包含 % 字符!").Validate(Str)
-            If Not CharactCheck = "" Then Return CharactCheck
+            If CharactCheck <> "" Then Return CharactCheck
             '检查特殊字符串
             '检查特殊字符串
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str)
             Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str)
-            If Not InvalidStrCheck = "" Then Return InvalidStrCheck
+            If InvalidStrCheck <> "" Then Return InvalidStrCheck
             '检查 NTFS 8.3 文件名(#4505)
             '检查 NTFS 8.3 文件名(#4505)
             If RegexCheck(Str, ".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
             If RegexCheck(Str, ".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
             '检查文件重名
             '检查文件重名
@@ -342,10 +348,10 @@ Public Class ValidateFolderPath
         If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\")
         If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\")
         '检查是否为空
         '检查是否为空
         Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
         Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
-        If Not LengthCheck = "" Then Return LengthCheck
+        If LengthCheck <> "" Then Return LengthCheck
         '检查长度
         '检查长度
         LengthCheck = New ValidateLength(1, 254).Validate(Str)
         LengthCheck = New ValidateLength(1, 254).Validate(Str)
-        If Not LengthCheck = "" Then Return LengthCheck
+        If LengthCheck <> "" Then Return LengthCheck
         '检查开头
         '检查开头
         If Str.StartsWithF("\\Mac\") Then GoTo Fin
         If Str.StartsWithF("\\Mac\") Then GoTo Fin
         For Each Drive As DriveInfo In My.Computer.FileSystem.Drives
         For Each Drive As DriveInfo In My.Computer.FileSystem.Drives

+ 283 - 142
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -2,6 +2,10 @@
 
 
     Public Enum CompType
     Public Enum CompType
         ''' <summary>
         ''' <summary>
+        ''' 允许任意种类,或种类未知。
+        ''' </summary>
+        Any = -1
+        ''' <summary> 
         ''' Mod。
         ''' Mod。
         ''' </summary>
         ''' </summary>
         [Mod] = 0
         [Mod] = 0
@@ -13,6 +17,19 @@
         ''' 资源包。
         ''' 资源包。
         ''' </summary>
         ''' </summary>
         ResourcePack = 2
         ResourcePack = 2
+        ''' <summary>
+        ''' 光影包。
+        ''' </summary>
+        Shader = 3
+        ''' <summary>
+        ''' CurseForge:数据包。
+        ''' Modrinth:数据包,或数据包与 Mod 的混合。
+        ''' </summary>
+        DataPack = 4
+        ''' <summary>
+        ''' 服务端插件。
+        ''' </summary>
+        Plugin = 5
     End Enum
     End Enum
     Public Enum CompModLoaderType
     Public Enum CompModLoaderType
         'https://docs.curseforge.com/?http#tocS_ModLoaderType
         'https://docs.curseforge.com/?http#tocS_ModLoaderType
@@ -61,8 +78,8 @@
                     If SplitedLine.Count >= 2 Then
                     If SplitedLine.Count >= 2 Then
                         Entry.ChineseName = SplitedLine(1)
                         Entry.ChineseName = SplitedLine(1)
                         If Entry.ChineseName.Contains("*") Then '处理 *
                         If Entry.ChineseName.Contains("*") Then '处理 *
-                            Entry.ChineseName = Entry.ChineseName.Replace("*", " (" &
-                                String.Join(" ", If(Entry.CurseForgeSlug, Entry.ModrinthSlug).Split("-").Select(Function(w) w.Substring(0, 1).ToUpper & w.Substring(1, w.Length - 1))) & ")")
+                            Entry.ChineseName = Entry.ChineseName.Replace("*",
+                                $" ({If(Entry.CurseForgeSlug, Entry.ModrinthSlug).Replace("-", " ").Capitalize})")
                         End If
                         End If
                     End If
                     End If
                     _CompDatabase.Add(Entry)
                     _CompDatabase.Add(Entry)
@@ -111,6 +128,7 @@
         Public ReadOnly FromCurseForge As Boolean
         Public ReadOnly FromCurseForge As Boolean
         ''' <summary>
         ''' <summary>
         ''' 工程的种类。
         ''' 工程的种类。
+        ''' 由于 Modrinth 混合使用 Mod 和数据包,结果不一定准确。
         ''' </summary>
         ''' </summary>
         Public ReadOnly Type As CompType
         Public ReadOnly Type As CompType
         ''' <summary>
         ''' <summary>
@@ -178,7 +196,9 @@
             Get
             Get
                 If Not LoadedDatabase Then
                 If Not LoadedDatabase Then
                     LoadedDatabase = True
                     LoadedDatabase = True
-                    If Type = CompType.Mod Then _DatabaseEntry = CompDatabase.FirstOrDefault(Function(c) If(FromCurseForge, c.CurseForgeSlug, c.ModrinthSlug) = Slug)
+                    If Type = CompType.Mod OrElse Type = CompType.DataPack Then
+                        _DatabaseEntry = CompDatabase.FirstOrDefault(Function(c) If(FromCurseForge, c.CurseForgeSlug, c.ModrinthSlug) = Slug)
+                    End If
                 End If
                 End If
                 Return _DatabaseEntry
                 Return _DatabaseEntry
             End Get
             End Get
@@ -254,6 +274,20 @@
                             LogoUrl = Data("logo")("thumbnailUrl")
                             LogoUrl = Data("logo")("thumbnailUrl")
                         End If
                         End If
                     End If
                     End If
+                    'Type
+                    If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then
+                        Type = CompType.Mod
+                    ElseIf Website.Contains("/modpacks/") Then
+                        Type = CompType.ModPack
+                    ElseIf Website.Contains("/resourcepacks/") Then
+                        Type = CompType.ResourcePack
+                    ElseIf Website.Contains("/texture-packs/") Then
+                        Type = CompType.ResourcePack
+                    ElseIf Website.Contains("/shaders/") Then
+                        Type = CompType.Shader
+                    Else
+                        Type = CompType.DataPack
+                    End If
                     'FileIndexes / GameVersions / ModLoaders
                     'FileIndexes / GameVersions / ModLoaders
                     ModLoaders = New List(Of CompModLoaderType)
                     ModLoaders = New List(Of CompModLoaderType)
                     Dim Files As New List(Of KeyValuePair(Of Integer, List(Of String))) 'FileId, GameVersions
                     Dim Files As New List(Of KeyValuePair(Of Integer, List(Of String))) 'FileId, GameVersions
@@ -274,20 +308,12 @@
                         Select(Function(v) CInt(Val(v.Split(".")(1).BeforeFirst("-")))).Where(Function(v) v > 0).
                         Select(Function(v) CInt(Val(v.Split(".")(1).BeforeFirst("-")))).Where(Function(v) v > 0).
                         Distinct.OrderByDescending(Function(v) v).ToList
                         Distinct.OrderByDescending(Function(v) v).ToList
                     ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList
                     ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList
-                    'Type
-                    If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then
-                        Type = CompType.Mod
-                    ElseIf Website.Contains("/modpacks/") Then
-                        Type = CompType.ModPack
-                    Else
-                        Type = CompType.ResourcePack
-                    End If
                     'Tags
                     'Tags
                     Tags = New List(Of String)
                     Tags = New List(Of String)
-                    For Each Category In If(Data("categories"), New JArray). '镜像源 API 可能丢失此字段:https://github.com/Hex-Dragon/PCL2/issues/4267#issuecomment-2254590831
+                    For Each Category In If(Data("categories"), New JArray). '镜像源 API 可能丢失此字段 (4267#issuecomment-2254590831)
                         Select(Of Integer)(Function(t) t("id")).Distinct.OrderByDescending(Function(c) c)
                         Select(Of Integer)(Function(t) t("id")).Distinct.OrderByDescending(Function(c) c)
                         Select Case Category
                         Select Case Category
-                        'Mod
+                            'Mod
                             Case 406 : Tags.Add("世界元素")
                             Case 406 : Tags.Add("世界元素")
                             Case 407 : Tags.Add("生物群系")
                             Case 407 : Tags.Add("生物群系")
                             Case 410 : Tags.Add("维度")
                             Case 410 : Tags.Add("维度")
@@ -311,7 +337,7 @@
                             Case 435 : Tags.Add("服务器")
                             Case 435 : Tags.Add("服务器")
                             Case 5191 : Tags.Add("改良")
                             Case 5191 : Tags.Add("改良")
                             Case 421 : Tags.Add("支持库")
                             Case 421 : Tags.Add("支持库")
-                        '整合包
+                            '整合包
                             Case 4484 : Tags.Add("多人")
                             Case 4484 : Tags.Add("多人")
                             Case 4479 : Tags.Add("硬核")
                             Case 4479 : Tags.Add("硬核")
                             Case 4483 : Tags.Add("战斗")
                             Case 4483 : Tags.Add("战斗")
@@ -321,17 +347,43 @@
                             Case 4475 : Tags.Add("冒险")
                             Case 4475 : Tags.Add("冒险")
                             Case 4476 : Tags.Add("探索")
                             Case 4476 : Tags.Add("探索")
                             Case 4477 : Tags.Add("小游戏")
                             Case 4477 : Tags.Add("小游戏")
-                            Case 4471 : Tags.Add("科幻")
+                            Case 4474 : Tags.Add("科幻")
                             Case 4736 : Tags.Add("空岛")
                             Case 4736 : Tags.Add("空岛")
                             Case 5128 : Tags.Add("原版改良")
                             Case 5128 : Tags.Add("原版改良")
                             Case 4487 : Tags.Add("FTB")
                             Case 4487 : Tags.Add("FTB")
                             Case 4480 : Tags.Add("基于地图")
                             Case 4480 : Tags.Add("基于地图")
                             Case 4481 : Tags.Add("轻量")
                             Case 4481 : Tags.Add("轻量")
                             Case 4482 : Tags.Add("大型")
                             Case 4482 : Tags.Add("大型")
-                                'FUTURE: Res
+                            '资源包
+                            Case 403 : Tags.Add("原版风")
+                            Case 400 : Tags.Add("写实风")
+                            Case 401 : Tags.Add("现代风")
+                            Case 402 : Tags.Add("中世纪")
+                            Case 399 : Tags.Add("蒸汽朋克")
+                            Case 5244 : Tags.Add("含字体")
+                            Case 404 : Tags.Add("动态效果")
+                            Case 4465 : Tags.Add("兼容 Mod")
+                            Case 393 : Tags.Add("16x")
+                            Case 394 : Tags.Add("32x")
+                            Case 395 : Tags.Add("64x")
+                            Case 396 : Tags.Add("128x")
+                            Case 397 : Tags.Add("256x")
+                            Case 398 : Tags.Add("超高清")
+                            Case 5193 : Tags.Add("数据包") '有这个 Tag 的项会从资源包请求中被移除
+                            '光影包
+                            Case 6553 : Tags.Add("写实风")
+                            Case 6554 : Tags.Add("幻想风")
+                            Case 6555 : Tags.Add("原版风")
+                            '数据包
+                            Case 6948 : Tags.Add("冒险")
+                            Case 6949 : Tags.Add("幻想")
+                            Case 6950 : Tags.Add("支持库")
+                            Case 6952 : Tags.Add("魔法")
+                            Case 6946 : Tags.Add("Mod 相关")
+                            Case 6951 : Tags.Add("科技")
+                            Case 6953 : Tags.Add("实用")
                         End Select
                         End Select
                     Next
                     Next
-                    If Not Tags.Any() Then Tags.Add("杂项")
 #End Region
 #End Region
                 Else
                 Else
 #Region "Modrinth"
 #Region "Modrinth"
@@ -353,9 +405,10 @@
                                        Distinct.OrderByDescending(Function(v) v).ToList
                                        Distinct.OrderByDescending(Function(v) v).ToList
                     'Type
                     'Type
                     Select Case Data("project_type").ToString
                     Select Case Data("project_type").ToString
-                        Case "mod" : Type = CompType.Mod
                         Case "modpack" : Type = CompType.ModPack
                         Case "modpack" : Type = CompType.ModPack
                         Case "resourcepack" : Type = CompType.ResourcePack
                         Case "resourcepack" : Type = CompType.ResourcePack
+                        Case "shader" : Type = CompType.Shader
+                        Case Else : Type = CompType.Mod 'Modrinth 将数据包标为 Mod
                     End Select
                     End Select
                     'Tags & ModLoaders
                     'Tags & ModLoaders
                     Tags = New List(Of String)
                     Tags = New List(Of String)
@@ -377,41 +430,78 @@
                             Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric)
                             Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric)
                             Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt)
                             Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt)
                             Case "neoforge" : ModLoaders.Add(CompModLoaderType.NeoForge)
                             Case "neoforge" : ModLoaders.Add(CompModLoaderType.NeoForge)
-                            'Mod
-                            Case "worldgen" : Tags.Add("世界元素")
+                            Case "datapack" : Type = CompType.DataPack '若包含数据包版本,则优先标为 DataPack
+                            '共用
                             Case "technology" : Tags.Add("科技")
                             Case "technology" : Tags.Add("科技")
+                            Case "magic" : Tags.Add("魔法")
+                            Case "adventure" : Tags.Add("冒险")
+                            Case "utility" : Tags.Add("实用")
+                            Case "optimization" : Tags.Add("性能优化")
+                            Case "vanilla-like" : Tags.Add("原版风")
+                            Case "realistic" : Tags.Add("写实风")
+                            'Mod/数据包
+                            Case "worldgen" : Tags.Add("世界元素")
                             Case "food" : Tags.Add("食物/烹饪")
                             Case "food" : Tags.Add("食物/烹饪")
                             Case "game-mechanics" : Tags.Add("游戏机制")
                             Case "game-mechanics" : Tags.Add("游戏机制")
                             Case "transportation" : Tags.Add("运输")
                             Case "transportation" : Tags.Add("运输")
                             Case "storage" : Tags.Add("仓储")
                             Case "storage" : Tags.Add("仓储")
-                            Case "magic" : Tags.Add("魔法")
-                            Case "adventure" : Tags.Add("冒险")
-                            Case "decoration" : Tags.Add("装饰")
-                            Case "mobs" : Tags.Add("生物")
-                            Case "equipment" : Tags.Add("装备")
-                            Case "optimization" : Tags.Add("性能优化")
+                            Case "decoration" : If Type <> CompType.ResourcePack Then Tags.Add("装饰")
+                            Case "mobs" : If Type <> CompType.ResourcePack Then Tags.Add("生物")
+                            Case "equipment" : If Type <> CompType.ResourcePack Then Tags.Add("装备")
                             Case "social" : Tags.Add("服务器")
                             Case "social" : Tags.Add("服务器")
-                            Case "utility" : Tags.Add("改良")
                             Case "library" : Tags.Add("支持库")
                             Case "library" : Tags.Add("支持库")
                             '整合包
                             '整合包
                             Case "multiplayer" : Tags.Add("多人")
                             Case "multiplayer" : Tags.Add("多人")
-                            Case "optimization" : Tags.Add("性能优化")
                             Case "challenging" : Tags.Add("硬核")
                             Case "challenging" : Tags.Add("硬核")
                             Case "combat" : Tags.Add("战斗")
                             Case "combat" : Tags.Add("战斗")
                             Case "quests" : Tags.Add("任务")
                             Case "quests" : Tags.Add("任务")
-                            Case "technology" : Tags.Add("科技")
-                            Case "magic" : Tags.Add("魔法")
-                            Case "adventure" : Tags.Add("冒险")
-                            Case "kitchen-sink" : Tags.Add("水槽包/大杂烩")
+                            Case "kitchen-sink" : Tags.Add("水槽包")
                             Case "lightweight" : Tags.Add("轻量")
                             Case "lightweight" : Tags.Add("轻量")
-                                'FUTURE: Res
+                            '资源包
+                            Case "simplistic" : Tags.Add("简洁")
+                            Case "combat" : Tags.Add("战斗")
+                            Case "tweaks" : Tags.Add("改良")
+
+                            Case "8x-" : Tags.Add("极简")
+                            Case "16x" : Tags.Add("16x")
+                            Case "32x" : Tags.Add("32x")
+                            Case "48x" : Tags.Add("48x")
+                            Case "64x" : Tags.Add("64x")
+                            Case "128x" : Tags.Add("128x")
+                            Case "256x" : Tags.Add("256x")
+                            Case "512x+" : Tags.Add("超高清")
+
+                            Case "audio" : Tags.Add("含声音")
+                            Case "fonts" : Tags.Add("含字体")
+                            Case "models" : Tags.Add("含模型")
+                            Case "gui" : Tags.Add("含 UI")
+                            Case "locale" : Tags.Add("含语言")
+                            Case "core-shaders" : Tags.Add("核心着色器")
+                            Case "modded" : Tags.Add("兼容 Mod")
+                            '光影包
+                            Case "fantasy" : Tags.Add("幻想风")
+                            Case "semi-realistic" : Tags.Add("半写实风")
+                            Case "cartoon" : Tags.Add("卡通风")
+                            '暂时不添加性能负荷 Tag
+                            'Case "potato" : Tags.Add("极低")
+                            'Case "low" : Tags.Add("低")
+                            'Case "medium" : Tags.Add("中")
+                            'Case "high" : Tags.Add("高")
+                            Case "colored-lighting" : Tags.Add("彩色光照")
+                            Case "path-tracing" : Tags.Add("路径追踪")
+                            Case "pbr" : Tags.Add("PBR")
+                            Case "reflections" : Tags.Add("反射")
+
+                            Case "iris" : Tags.Add("Iris")
+                            Case "optifine" : Tags.Add("OptiFine")
+                            Case "vanilla" : Tags.Add("原版可用")
                         End Select
                         End Select
                     Next
                     Next
-                    If Not Tags.Any() Then Tags.Add("杂项")
-                    Tags.Sort()
-                    ModLoaders.Sort()
 #End Region
 #End Region
                 End If
                 End If
+                If Not Tags.Any() Then Tags.Add("其他")
+                Tags.Sort()
+                ModLoaders.Sort()
             End If
             End If
             '保存缓存
             '保存缓存
             CompProjectCache(Id) = Me
             CompProjectCache(Id) = Me
@@ -534,7 +624,7 @@
                 NewItem.ColumnVersion1.Width = New GridLength(0)
                 NewItem.ColumnVersion1.Width = New GridLength(0)
                 NewItem.ColumnVersion2.MaxWidth = 0
                 NewItem.ColumnVersion2.MaxWidth = 0
                 NewItem.ColumnVersion3.Width = New GridLength(0)
                 NewItem.ColumnVersion3.Width = New GridLength(0)
-            ElseIf ShowMcVersionDesc AndAlso ShowMcVersionDesc Then
+            ElseIf ShowMcVersionDesc AndAlso ShowLoaderDesc Then
                 '全部显示
                 '全部显示
                 NewItem.LabVersion.Text = If(ModLoaderDescriptionPart = "", "", ModLoaderDescriptionPart & " ") & GameVersionDescription
                 NewItem.LabVersion.Text = If(ModLoaderDescriptionPart = "", "", ModLoaderDescriptionPart & " ") & GameVersionDescription
             ElseIf ShowMcVersionDesc Then
             ElseIf ShowMcVersionDesc Then
@@ -664,8 +754,8 @@ NoSubtitle:
             If FromCurseForge = Project.FromCurseForge Then Return False
             If FromCurseForge = Project.FromCurseForge Then Return False
             'Mod 加载器一致
             'Mod 加载器一致
             If ModLoaders.Count <> Project.ModLoaders.Count OrElse ModLoaders.Except(Project.ModLoaders).Any() Then Return False
             If ModLoaders.Count <> Project.ModLoaders.Count OrElse ModLoaders.Except(Project.ModLoaders).Any() Then Return False
-            'MC 版本一致
-            If GameVersions.Count <> Project.GameVersions.Count OrElse GameVersions.Except(Project.GameVersions).Any() Then Return False
+            '若不为光影,则要求 MC 版本一致
+            If Type <> CompType.Shader AndAlso (GameVersions.Count <> Project.GameVersions.Count OrElse GameVersions.Except(Project.GameVersions).Any()) Then Return False
             '最近更新时间差距在一周以内
             '最近更新时间差距在一周以内
             If LastUpdate IsNot Nothing AndAlso Project.LastUpdate IsNot Nothing AndAlso
             If LastUpdate IsNot Nothing AndAlso Project.LastUpdate IsNot Nothing AndAlso
                Math.Abs((LastUpdate - Project.LastUpdate).Value.TotalDays) > 7 Then Return False
                Math.Abs((LastUpdate - Project.LastUpdate).Value.TotalDays) > 7 Then Return False
@@ -775,8 +865,12 @@ NoSubtitle:
                     Address += "&classId=6"
                     Address += "&classId=6"
                 Case CompType.ModPack
                 Case CompType.ModPack
                     Address += "&classId=4471"
                     Address += "&classId=4471"
+                Case CompType.DataPack
+                    Address += "&classId=6945"
+                Case CompType.Shader
+                    Address += "&classId=6552"
                 Case CompType.ResourcePack
                 Case CompType.ResourcePack
-                    'FUTURE: Res
+                    Address += "&classId=12"
             End Select
             End Select
             Address += "&categoryId=" & If(Tag = "", "0", Tag.BeforeFirst("/"))
             Address += "&categoryId=" & If(Tag = "", "0", Tag.BeforeFirst("/"))
             If ModLoader <> CompModLoaderType.Any Then Address += "&modLoaderType=" & CType(ModLoader, Integer)
             If ModLoader <> CompModLoaderType.Any Then Address += "&modLoaderType=" & CType(ModLoader, Integer)
@@ -856,38 +950,39 @@ NoSubtitle:
     ''' 根据搜索请求获取一系列的工程列表。需要基于加载器运行。
     ''' 根据搜索请求获取一系列的工程列表。需要基于加载器运行。
     ''' </summary>
     ''' </summary>
     Public Sub CompProjectsGet(Task As LoaderTask(Of CompProjectRequest, Integer))
     Public Sub CompProjectsGet(Task As LoaderTask(Of CompProjectRequest, Integer))
-        Dim Storage = Task.Input.Storage '避免多线程问题
-
-        If Task.Input.Storage.Results.Count >= Task.Input.TargetResultCount Then
-            Log($"[Comp] 已有 {Task.Input.Storage.Results.Count} 个结果,多于所需的 {Task.Input.TargetResultCount} 个结果,结束处理")
-            Exit Sub
-        ElseIf Not Task.Input.CanContinue Then
-            If Not Task.Input.Storage.Results.Any() Then
+        Dim Request As CompProjectRequest = Task.Input
+        Dim Storage = Request.Storage '避免多线程问题
+
+        If Request.Storage.Results.Count >= Request.TargetResultCount Then
+            Log($"[Comp] 已有 {Request.Storage.Results.Count} 个结果,多于所需的 {Request.TargetResultCount} 个结果,结束处理")
+            Return
+        ElseIf Not Request.CanContinue Then
+            If Not Request.Storage.Results.Any() Then
                 Throw New Exception("没有符合条件的结果")
                 Throw New Exception("没有符合条件的结果")
             Else
             Else
-                Log($"[Comp] 已有 {Task.Input.Storage.Results.Count} 个结果,少于所需的 {Task.Input.TargetResultCount} 个结果,但无法继续获取,结束处理")
-                Exit Sub
+                Log($"[Comp] 已有 {Request.Storage.Results.Count} 个结果,少于所需的 {Request.TargetResultCount} 个结果,但无法继续获取,结束处理")
+                Return
             End If
             End If
         End If
         End If
 
 
 #Region "拒绝 1.13- Quilt(这个版本根本没有 Quilt)"
 #Region "拒绝 1.13- Quilt(这个版本根本没有 Quilt)"
 
 
-        If Task.Input.ModLoader = CompModLoaderType.Quilt AndAlso VersionSortInteger(If(Task.Input.GameVersion, "1.15"), "1.14") = -1 Then
-            Throw New Exception("Quilt 不支持 Minecraft " & Task.Input.GameVersion)
+        If Request.ModLoader = CompModLoaderType.Quilt AndAlso VersionSortInteger(If(Request.GameVersion, "1.15"), "1.14") = -1 Then
+            Throw New Exception("Quilt 不支持 Minecraft " & Request.GameVersion)
         End If
         End If
 
 
 #End Region
 #End Region
 
 
 #Region "处理搜索文本,赋值回 Task.Input.SearchText"
 #Region "处理搜索文本,赋值回 Task.Input.SearchText"
 
 
-        Dim RawFilter As String = If(Task.Input.SearchText, "").Trim
-        Task.Input.SearchText = RawFilter
+        Dim RawFilter As String = If(Request.SearchText, "").Trim
+        Request.SearchText = RawFilter
         RawFilter = RawFilter.ToLower
         RawFilter = RawFilter.ToLower
         Log("[Comp] 工程列表搜索原始文本:" & RawFilter)
         Log("[Comp] 工程列表搜索原始文本:" & RawFilter)
 
 
         '中文请求关键字处理
         '中文请求关键字处理
         Dim IsChineseSearch As Boolean = RegexCheck(RawFilter, "[\u4e00-\u9fbb]") AndAlso Not String.IsNullOrEmpty(RawFilter)
         Dim IsChineseSearch As Boolean = RegexCheck(RawFilter, "[\u4e00-\u9fbb]") AndAlso Not String.IsNullOrEmpty(RawFilter)
-        If IsChineseSearch AndAlso Task.Input.Type = CompType.Mod Then
+        If IsChineseSearch AndAlso (Request.Type = CompType.Mod OrElse Request.Type = CompType.DataPack) Then
             '构造搜索请求
             '构造搜索请求
             Dim SearchEntries As New List(Of SearchEntry(Of CompDatabaseEntry))
             Dim SearchEntries As New List(Of SearchEntry(Of CompDatabaseEntry))
             For Each Entry In CompDatabase
             For Each Entry In CompDatabase
@@ -900,7 +995,7 @@ NoSubtitle:
                 })
                 })
             Next
             Next
             '获取搜索结果
             '获取搜索结果
-            Dim SearchResults = Search(SearchEntries, Task.Input.SearchText, 3)
+            Dim SearchResults = Search(SearchEntries, Request.SearchText, 3)
             If Not SearchResults.Any() Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
             If Not SearchResults.Any() Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
             Dim SearchResult As String = ""
             Dim SearchResult As String = ""
             For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
             For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
@@ -918,14 +1013,14 @@ NoSubtitle:
                 If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For
                 If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For
                 RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " "
                 RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " "
             Next
             Next
-            Task.Input.SearchText = RealFilter
+            Request.SearchText = RealFilter
             Log("[Comp] 中文搜索最终关键词:" & RealFilter, LogLevel.Developer)
             Log("[Comp] 中文搜索最终关键词:" & RealFilter, LogLevel.Developer)
         End If
         End If
 
 
         '驼峰英文请求关键字处理
         '驼峰英文请求关键字处理
-        Dim SpacedKeywords = Task.Input.SearchText.RegexReplace("([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)", "$& ")
-        Dim ConnectedKeywords = Task.Input.SearchText.Replace(" ", "")
-        Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Task.Input.SearchText, ConnectedKeywords & " " & RawFilter)).ToLower
+        Dim SpacedKeywords = Request.SearchText.RegexReplace("([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)", "$& ")
+        Dim ConnectedKeywords = Request.SearchText.Replace(" ", "")
+        Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Request.SearchText, ConnectedKeywords & " " & RawFilter)).ToLower
 
 
         '最终处理关键字:分割、去重
         '最终处理关键字:分割、去重
         Dim RightKeywords As New List(Of String)
         Dim RightKeywords As New List(Of String)
@@ -939,15 +1034,15 @@ NoSubtitle:
             RightKeywords.Add(Keyword)
             RightKeywords.Add(Keyword)
         Next
         Next
         If RawFilter.Length > 0 AndAlso Not RightKeywords.Any() Then
         If RawFilter.Length > 0 AndAlso Not RightKeywords.Any() Then
-            Task.Input.SearchText = RawFilter '全都被过滤掉了
+            Request.SearchText = RawFilter '全都被过滤掉了
         Else
         Else
-            Task.Input.SearchText = Join(RightKeywords.Distinct.ToList, " ").ToLower
+            Request.SearchText = Join(RightKeywords.Distinct.ToList, " ").ToLower
         End If
         End If
 
 
         '例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
         '例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
-        If RawFilter.Replace(" ", "").ContainsF("optiforge", True) Then Task.Input.SearchText = "optiforge"
-        If RawFilter.Replace(" ", "").ContainsF("optifabric", True) Then Task.Input.SearchText = "optifabric"
-        Log("[Comp] 工程列表搜索最终文本:" & Task.Input.SearchText, LogLevel.Debug)
+        If RawFilter.Replace(" ", "").ContainsF("optiforge", True) Then Request.SearchText = "optiforge"
+        If RawFilter.Replace(" ", "").ContainsF("optifabric", True) Then Request.SearchText = "optifabric"
+        Log("[Comp] 工程列表搜索最终文本:" & Request.SearchText, LogLevel.Debug)
         Task.Progress = 0.1
         Task.Progress = 0.1
 
 
 #End Region
 #End Region
@@ -963,10 +1058,17 @@ Retry:
         Dim ModrinthThread As Thread = Nothing
         Dim ModrinthThread As Thread = Nothing
         Dim ResultsLock As New Object
         Dim ResultsLock As New Object
 
 
+        '在 1.14-,部分老 Mod 没有设置支持的加载器,因此添加 Forge 筛选就会出现遗漏
+        '所以,在发起请求时不筛选加载器,然后在返回的结果中自行筛除不是 Forge 的 Mod
+        Dim IsOldForgeRequest = Request.ModLoader = CompModLoaderType.Forge AndAlso Request.GameVersion?.Contains(".") AndAlso Val(Request.GameVersion.Split(".")(1)) < 14
+        If IsOldForgeRequest Then Request.ModLoader = CompModLoaderType.Any
+        Dim CurseForgeUrl As String = Request.GetCurseForgeAddress()
+        Dim ModrinthUrl As String = Request.GetModrinthAddress()
+        If IsOldForgeRequest Then Request.ModLoader = CompModLoaderType.Forge '设置回去
+
         Try
         Try
 
 
             '启动 CurseForge 线程
             '启动 CurseForge 线程
-            Dim CurseForgeUrl As String = Task.Input.GetCurseForgeAddress()
             Dim CurseForgeFailed As Boolean = False
             Dim CurseForgeFailed As Boolean = False
             If CurseForgeUrl IsNot Nothing Then
             If CurseForgeUrl IsNot Nothing Then
                 CurseForgeThread = RunInNewThread(
                 CurseForgeThread = RunInNewThread(
@@ -974,11 +1076,13 @@ Retry:
                     Try
                     Try
                         '获取工程列表
                         '获取工程列表
                         Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
                         Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
-                        Dim RequestResult As JObject = DlModRequest(CurseForgeUrl, IsJson:=True)
+                        Dim RequestResult As JObject = DlModRequest(CurseForgeUrl)
                         Task.Progress += 0.2
                         Task.Progress += 0.2
                         Dim ProjectList As New List(Of CompProject)
                         Dim ProjectList As New List(Of CompProject)
                         For Each JsonEntry As JObject In RequestResult("data")
                         For Each JsonEntry As JObject In RequestResult("data")
-                            ProjectList.Add(New CompProject(JsonEntry))
+                            Dim Project As New CompProject(JsonEntry)
+                            If Request.Type = CompType.ResourcePack AndAlso Project.Tags.Contains("数据包") Then Continue For 'CurseForge 将一些数据包分类成了资源包
+                            ProjectList.Add(Project)
                         Next
                         Next
                         '更新结果
                         '更新结果
                         SyncLock ResultsLock
                         SyncLock ResultsLock
@@ -997,14 +1101,13 @@ Retry:
             End If
             End If
 
 
             '启动 Modrinth 线程
             '启动 Modrinth 线程
-            Dim ModrinthUrl As String = Task.Input.GetModrinthAddress()
             Dim ModrinthFailed As Boolean = False
             Dim ModrinthFailed As Boolean = False
             If ModrinthUrl IsNot Nothing Then
             If ModrinthUrl IsNot Nothing Then
                 ModrinthThread = RunInNewThread(
                 ModrinthThread = RunInNewThread(
                 Sub()
                 Sub()
                     Try
                     Try
                         Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl)
                         Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl)
-                        Dim RequestResult As JObject = DlModRequest(ModrinthUrl, IsJson:=True)
+                        Dim RequestResult As JObject = DlModRequest(ModrinthUrl)
                         Task.Progress += 0.2
                         Task.Progress += 0.2
                         Dim ProjectList As New List(Of CompProject)
                         Dim ProjectList As New List(Of CompProject)
                         For Each JsonEntry As JObject In RequestResult("hits")
                         For Each JsonEntry As JObject In RequestResult("hits")
@@ -1013,7 +1116,6 @@ Retry:
                         '更新结果
                         '更新结果
                         SyncLock ResultsLock
                         SyncLock ResultsLock
                             For Each Project In ProjectList
                             For Each Project In ProjectList
-                                If Task.Input.Type = CompType.Mod AndAlso Not Project.ModLoaders.Any() Then Continue For '过滤插件(#2458)
                                 RawResults.Add(Project)
                                 RawResults.Add(Project)
                             Next
                             Next
                         End SyncLock
                         End SyncLock
@@ -1031,9 +1133,14 @@ Retry:
 
 
             '等待线程结束
             '等待线程结束
             If CurseForgeThread IsNot Nothing Then CurseForgeThread.Join()
             If CurseForgeThread IsNot Nothing Then CurseForgeThread.Join()
-            If Task.IsAborted Then Exit Sub '会自动触发 Finally
+            If Task.IsAborted Then Return '会自动触发 Finally
             If ModrinthThread IsNot Nothing Then ModrinthThread.Join()
             If ModrinthThread IsNot Nothing Then ModrinthThread.Join()
-            If Task.IsAborted Then Exit Sub
+            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
             Storage.ErrorMessage = Nothing
@@ -1041,11 +1148,11 @@ Retry:
                 If [Error] IsNot Nothing Then
                 If [Error] IsNot Nothing Then
                     Throw [Error]
                     Throw [Error]
                 Else
                 Else
-                    If IsChineseSearch AndAlso Task.Input.Type <> CompType.Mod Then
-                        Throw New Exception($"{If(Task.Input.Type = CompType.ModPack, "整合包", "资源包")}搜索仅支持英文")
-                    ElseIf Task.Input.Source = CompSourceType.CurseForge AndAlso Task.Input.Tag.StartsWithF("/") Then
+                    If IsChineseSearch AndAlso Not (Request.Type = CompType.Mod OrElse Request.Type = CompType.DataPack) Then
+                        Throw New Exception("没有搜索结果,请尝试使用英文搜索")
+                    ElseIf Request.Source = CompSourceType.CurseForge AndAlso Request.Tag.StartsWithF("/") Then
                         Throw New Exception("CurseForge 不兼容所选的类型")
                         Throw New Exception("CurseForge 不兼容所选的类型")
-                    ElseIf Task.Input.Source = CompSourceType.Modrinth AndAlso Task.Input.Tag.EndsWithF("/") Then
+                    ElseIf Request.Source = CompSourceType.Modrinth AndAlso Request.Tag.EndsWithF("/") Then
                         Throw New Exception("Modrinth 不兼容所选的类型")
                         Throw New Exception("Modrinth 不兼容所选的类型")
                     Else
                     Else
                         Throw New Exception("没有搜索结果")
                         Throw New Exception("没有搜索结果")
@@ -1054,15 +1161,15 @@ Retry:
             ElseIf [Error] IsNot Nothing Then
             ElseIf [Error] IsNot Nothing Then
                 '有结果但是有错误
                 '有结果但是有错误
                 If CurseForgeFailed Then
                 If CurseForgeFailed Then
-                    Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,结果可能不全。{vbCrLf}请尝试使用 VPN 或加速器以改善网络。"
+                    Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,搜索结果可能不全。{vbCrLf}请稍后再试,或使用 VPN 改善网络环境。"
                 Else
                 Else
-                    Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,结果可能不全。{vbCrLf}请尝试使用 VPN 或加速器以改善网络。"
+                    Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。{vbCrLf}请稍后再试,或使用 VPN 改善网络环境。"
                 End If
                 End If
             End If
             End If
 
 
         Finally
         Finally
-            If CurseForgeThread IsNot Nothing Then CurseForgeThread.Interrupt()
-            If ModrinthThread IsNot Nothing Then ModrinthThread.Interrupt()
+            CurseForgeThread?.Interrupt()
+            ModrinthThread?.Interrupt()
         End Try
         End Try
 
 
 #End Region
 #End Region
@@ -1085,9 +1192,9 @@ Retry:
 
 
 #Region "检查结果数量,如果不足且可继续,会继续加载下一页"
 #Region "检查结果数量,如果不足且可继续,会继续加载下一页"
 
 
-        If RealResults.Count + Storage.Results.Count < Task.Input.TargetResultCount Then
-            Log($"[Comp] 总结果数需求最少 {Task.Input.TargetResultCount} 个,仅获得了 {RealResults.Count + Storage.Results.Count} 个")
-            If Task.Input.CanContinue AndAlso [Error] Is Nothing Then '如果有下载源失败则不再重试,这时候重试可能导致无限循环
+        If RealResults.Count + Storage.Results.Count < Request.TargetResultCount Then
+            Log($"[Comp] 总结果数需求最少 {Request.TargetResultCount} 个,仅获得了 {RealResults.Count + Storage.Results.Count} 个")
+            If Request.CanContinue AndAlso [Error] Is Nothing Then '如果有下载源失败则不再重试,这时候重试可能导致无限循环
                 Log("[Comp] 将继续尝试加载下一页")
                 Log("[Comp] 将继续尝试加载下一页")
                 GoTo Retry
                 GoTo Retry
             Else
             Else
@@ -1100,10 +1207,23 @@ Retry:
 #Region "将结果排序并添加"
 #Region "将结果排序并添加"
 
 
         Dim Scores As New Dictionary(Of CompProject, Double) '排序分
         Dim Scores As New Dictionary(Of CompProject, Double) '排序分
-        If String.IsNullOrEmpty(Task.Input.SearchText) Then
+        Dim GetDownloadCountMult =
+        Function(Project As CompProject) As Double
+            Select Case Request.Type
+                Case CompType.Mod, CompType.ModPack
+                    Return If(Project.FromCurseForge, 1, 7)
+                Case CompType.DataPack
+                    Return If(Project.FromCurseForge, 10, 1)
+                Case CompType.ResourcePack, CompType.Shader
+                    Return If(Project.FromCurseForge, 1, 5)
+                Case Else
+                    Return 1
+            End Select
+        End Function
+        If String.IsNullOrEmpty(Request.SearchText) Then
             '如果没有搜索文本,按下载量将结果排序
             '如果没有搜索文本,按下载量将结果排序
             For Each Result As CompProject In RealResults
             For Each Result As CompProject In RealResults
-                Scores.Add(Result, Result.DownloadCount * If(Result.FromCurseForge, 1, 5))
+                Scores.Add(Result, Result.DownloadCount * GetDownloadCountMult(Result))
             Next
             Next
         Else
         Else
             '如果有搜索文本,按关联度将结果排序
             '如果有搜索文本,按关联度将结果排序
@@ -1111,7 +1231,7 @@ Retry:
             Dim Entry As New List(Of SearchEntry(Of CompProject))
             Dim Entry As New List(Of SearchEntry(Of CompProject))
             For Each Result As CompProject In RealResults
             For Each Result As CompProject In RealResults
                 Scores.Add(Result, If(Result.WikiId > 0, 0.2, 0) +
                 Scores.Add(Result, If(Result.WikiId > 0, 0.2, 0) +
-                           Math.Log10(Math.Max(Result.DownloadCount, 1) * If(Result.FromCurseForge, 1, 5)) / 9)
+                           Math.Log10(Math.Max(Result.DownloadCount, 1) * GetDownloadCountMult(Result)) / 9)
                 Entry.Add(New SearchEntry(Of CompProject) With {.Item = Result, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
                 Entry.Add(New SearchEntry(Of CompProject) With {.Item = Result, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
                           New KeyValuePair(Of String, Double)(If(IsChineseSearch, Result.TranslatedName, Result.RawName), 1),
                           New KeyValuePair(Of String, Double)(If(IsChineseSearch, Result.TranslatedName, Result.RawName), 1),
                           New KeyValuePair(Of String, Double)(Result.Description, 0.05)}})
                           New KeyValuePair(Of String, Double)(Result.Description, 0.05)}})
@@ -1194,7 +1314,7 @@ Retry:
                     Case CompFileStatus.Beta
                     Case CompFileStatus.Beta
                         Return If(ModeDebug, "Beta 版", "测试版")
                         Return If(ModeDebug, "Beta 版", "测试版")
                     Case Else
                     Case Else
-                        Return If(ModeDebug, "Alpha 版", "测试版")
+                        Return If(ModeDebug, "Alpha 版", "早期测试版")
                 End Select
                 End Select
             End Get
             End Get
         End Property
         End Property
@@ -1234,7 +1354,7 @@ Retry:
         ''' </summary>
         ''' </summary>
         ''' <param name="LocalAddress">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
         ''' <param name="LocalAddress">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
         Public Function ToNetFile(LocalAddress As String) As NetFile
         Public Function ToNetFile(LocalAddress As String) As NetFile
-            Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), UseBrowserUserAgent:=True)
+            Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), SimulateBrowserHeaders:=True)
         End Function
         End Function
 
 
         '实例化
         '实例化
@@ -1242,8 +1362,8 @@ Retry:
         ''' <summary>
         ''' <summary>
         ''' 从文件 Json 中初始化实例。若出错会抛出异常。
         ''' 从文件 Json 中初始化实例。若出错会抛出异常。
         ''' </summary>
         ''' </summary>
-        Public Sub New(Data As JObject, Type As CompType)
-            Me.Type = Type
+        Public Sub New(Data As JObject, DefaultType As CompType)
+            Type = DefaultType
             If Data.ContainsKey("FromCurseForge") Then
             If Data.ContainsKey("FromCurseForge") Then
 #Region "CompJson"
 #Region "CompJson"
                 FromCurseForge = Data("FromCurseForge").ToObject(Of Boolean)
                 FromCurseForge = Data("FromCurseForge").ToObject(Of Boolean)
@@ -1275,13 +1395,12 @@ Retry:
                     If Hash Is Nothing Then Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")
                     If Hash Is Nothing Then Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")
                     'DownloadAddress
                     'DownloadAddress
                     Dim Url = Data("downloadUrl").ToString
                     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)) '对文件名进行编码
-                    DownloadUrls = HandleCurseForgeDownloadUrls(Url) '对脑残 CurseForge 的下载地址进行多种修正
-                    DownloadUrls.AddRange(DownloadUrls.Select(Function(u) DlSourceModGet(u)).ToList) '添加镜像源,这个写法是为了让镜像源排在后面
+                    If Url = "" Then Url = $"https://edge.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}"
+                    DownloadUrls = HandleCurseForgeDownloadUrls(Url.Replace(FileName, WebUtility.UrlEncode(FileName))) '对脑残 CurseForge 的下载地址进行多种修正
+                    DownloadUrls.Add(DlSourceModGet(Url)) '添加镜像源;注意 MCIM 源不支持 URL 编码后的文件名,必须传入 URL 编码前的文件名
                     DownloadUrls = DownloadUrls.Distinct.ToList '最终去重
                     DownloadUrls = DownloadUrls.Distinct.ToList '最终去重
                     'Dependencies
                     'Dependencies
-                    If Type = CompType.Mod Then
+                    If Data.ContainsKey("dependencies") Then
                         RawDependencies = Data("dependencies").
                         RawDependencies = Data("dependencies").
                             Where(Function(d) d("relationType").ToObject(Of Integer) = 3 AndAlso '种类为依赖
                             Where(Function(d) d("relationType").ToObject(Of Integer) = 3 AndAlso '种类为依赖
                                               d("modId").ToObject(Of Integer) <> 306612 AndAlso d("modId").ToObject(Of Integer) <> 634179). '排除 Fabric API 和 Quilt API
                                               d("modId").ToObject(Of Integer) <> 306612 AndAlso d("modId").ToObject(Of Integer) <> 634179). '排除 Fabric API 和 Quilt API
@@ -1292,7 +1411,7 @@ Retry:
                     GameVersions = RawVersions.Where(Function(v) v.StartsWithF("1.")).Select(Function(v) v.Replace("-snapshot", " 预览版")).ToList
                     GameVersions = RawVersions.Where(Function(v) v.StartsWithF("1.")).Select(Function(v) v.Replace("-snapshot", " 预览版")).ToList
                     If GameVersions.Count > 1 Then
                     If GameVersions.Count > 1 Then
                         GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList
                         GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList
-                        If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)}
+                        If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本
                     ElseIf GameVersions.Count = 1 Then
                     ElseIf GameVersions.Count = 1 Then
                         GameVersions = GameVersions.ToList
                         GameVersions = GameVersions.ToList
                     Else
                     Else
@@ -1319,8 +1438,27 @@ Retry:
                         DownloadUrls = New List(Of String) From {File("url"), DlSourceModGet(File("url"))}.Distinct.ToList '同时添加了镜像源
                         DownloadUrls = New List(Of String) From {File("url"), DlSourceModGet(File("url"))}.Distinct.ToList '同时添加了镜像源
                         Hash = File("hashes")("sha1")
                         Hash = File("hashes")("sha1")
                     End If
                     End If
+                    'ModLoaders
+                    '结果可能混杂着 Mod、数据包和服务端插件
+                    Dim RawLoaders As List(Of String) = Data("loaders").Select(Function(v) v.ToString).ToList
+                    ModLoaders = New List(Of CompModLoaderType)
+                    If Type = CompType.Mod Then '以尽量宽容的方式检测加载器,以免同时兼容两种的项被删除
+                        If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then Type = CompType.Plugin 'Veinminer Enchantment 同时支持服务端与 Fabric
+                        If RawLoaders.Contains("datapack") Then Type = CompType.DataPack
+                        If RawLoaders.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge) : Type = CompType.Mod
+                        If RawLoaders.Contains("neoforge") Then ModLoaders.Add(CompModLoaderType.NeoForge) : Type = CompType.Mod
+                        If RawLoaders.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric) : Type = CompType.Mod
+                        If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt) : Type = CompType.Mod
+                    ElseIf Type = CompType.DataPack Then
+                        If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then Type = CompType.Plugin
+                        If RawLoaders.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge) : Type = CompType.Mod
+                        If RawLoaders.Contains("neoforge") Then ModLoaders.Add(CompModLoaderType.NeoForge) : Type = CompType.Mod
+                        If RawLoaders.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric) : Type = CompType.Mod
+                        If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt) : Type = CompType.Mod
+                        If RawLoaders.Contains("datapack") Then Type = CompType.DataPack
+                    End If
                     'Dependencies
                     'Dependencies
-                    If Type = CompType.Mod Then
+                    If Data.ContainsKey("dependencies") Then
                         RawDependencies = Data("dependencies").
                         RawDependencies = Data("dependencies").
                             Where(Function(d) d("dependency_type") = "required" AndAlso '种类为依赖
                             Where(Function(d) d("dependency_type") = "required" AndAlso '种类为依赖
                                               d("project_id") <> "P7dR8mSH" AndAlso d("project_id") <> "qvIfYCYJ" AndAlso '排除 Fabric API 和 Quilt API
                                               d("project_id") <> "P7dR8mSH" AndAlso d("project_id") <> "qvIfYCYJ" AndAlso '排除 Fabric API 和 Quilt API
@@ -1333,7 +1471,7 @@ Retry:
                                                Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).ToList
                                                Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).ToList
                     If GameVersions.Count > 1 Then
                     If GameVersions.Count > 1 Then
                         GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList
                         GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList
-                        If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)}
+                        If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本
                     ElseIf GameVersions.Count = 1 Then
                     ElseIf GameVersions.Count = 1 Then
                         '无需处理
                         '无需处理
                     ElseIf RawVersions.Any(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]{1}")) Then
                     ElseIf RawVersions.Any(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]{1}")) Then
@@ -1341,13 +1479,6 @@ Retry:
                     Else
                     Else
                         GameVersions = New List(Of String) From {"未知版本"}
                         GameVersions = New List(Of String) From {"未知版本"}
                     End If
                     End If
-                    'ModLoaders
-                    Dim RawLoaders As List(Of String) = Data("loaders").Select(Function(v) v.ToString).ToList
-                    ModLoaders = New List(Of CompModLoaderType)
-                    If RawLoaders.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge)
-                    If RawLoaders.Contains("neoforge") Then ModLoaders.Add(CompModLoaderType.NeoForge)
-                    If RawLoaders.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric)
-                    If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt)
 #End Region
 #End Region
                 End If
                 End If
             End If
             End If
@@ -1358,9 +1489,10 @@ Retry:
         ''' </summary>
         ''' </summary>
         Public Shared Function HandleCurseForgeDownloadUrls(Url As String) As List(Of String)
         Public Shared Function HandleCurseForgeDownloadUrls(Url As String) As List(Of String)
             Return {
             Return {
-                Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"),
+                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"),
                 Url.Replace("-service.overwolf.wtf", ".forgecdn.net"),
-                Url.Replace("://edge", "://media"),
+                Url.Replace("://media.", "://edge."),
                 Url
                 Url
             }.Distinct.ToList
             }.Distinct.ToList
         End Function
         End Function
@@ -1395,12 +1527,8 @@ Retry:
             Dim Title As String = If(BadDisplayName, FileName, DisplayName)
             Dim Title As String = If(BadDisplayName, FileName, DisplayName)
             Dim Info As New List(Of String)
             Dim Info As New List(Of String)
             If Title <> FileName.BeforeLast(".") Then Info.Add(FileName.BeforeLast("."))
             If Title <> FileName.BeforeLast(".") Then Info.Add(FileName.BeforeLast("."))
-            Select Case Type
-                Case CompType.Mod
-                    If Dependencies.Any Then Info.Add(Dependencies.Count & " 个前置 Mod")
-                Case CompType.ModPack
-                    If GameVersions.All(Function(v) v.Contains("w")) Then Info.Add($"游戏版本 {Join(GameVersions, "、")}")
-            End Select
+            If Dependencies.Any Then Info.Add(Dependencies.Count & " 项前置")
+            If GameVersions.All(Function(v) Not IsVersionNameLikeRelease(v)) Then Info.Add($"游戏版本 {Join(GameVersions, "、")}")
             If DownloadCount > 0 Then 'CurseForge 的下载次数经常错误地返回 0
             If DownloadCount > 0 Then 'CurseForge 的下载次数经常错误地返回 0
                 Info.Add("下载 " & If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次", DownloadCount & " 次"))
                 Info.Add("下载 " & If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次", DownloadCount & " 次"))
             End If
             End If
@@ -1461,50 +1589,63 @@ Retry:
         If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存
         If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存
             TargetProject = CompProjectCache(ProjectId)
             TargetProject = CompProjectCache(ProjectId)
         ElseIf FromCurseForge Then 'CurseForge
         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
         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
         End If
         '获取工程对象的文件列表
         '获取工程对象的文件列表
-        If Not CompFilesCache.ContainsKey(ProjectId) Then '有缓存也不能直接返回,这时候前置 Mod 可能没获取(#5173)
+        If Not CompFilesCache.ContainsKey(ProjectId) Then '有缓存也不能直接返回,这时候前置可能没获取(#5173)
             Log("[Comp] 开始获取文件列表:" & ProjectId)
             Log("[Comp] 开始获取文件列表:" & ProjectId)
-            Dim ResultJsonArray As JArray
+            Dim ResultJsonArray As JArray = Nothing
             If FromCurseForge Then
             If FromCurseForge Then
                 'CurseForge
                 '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
             Else
                 'Modrinth
                 '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
             End If
             CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
             CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
-                Where(Function(a) a.Available).ToList.Distinct(Function(a, b) a.Id = b.Id) 'CurseForge 可能会重复返回相同项(#1330)
+                Where(Function(a) a.Available).ToList.
+                Distinct(Function(a, b) a.Id = b.Id) 'CurseForge 可能会重复返回相同项(#1330)
         End If
         End If
-        '获取前置 Mod 列表
-        If TargetProject.Type <> CompType.Mod Then Return CompFilesCache(ProjectId)
+        '获取前置列表
         Dim Deps As List(Of String) = CompFilesCache(ProjectId).SelectMany(Function(f) f.RawDependencies).Distinct().ToList
         Dim Deps As List(Of String) = CompFilesCache(ProjectId).SelectMany(Function(f) f.RawDependencies).Distinct().ToList
         Dim UndoneDeps = Deps.Where(Function(f) Not CompProjectCache.ContainsKey(f)).ToList
         Dim UndoneDeps = Deps.Where(Function(f) Not CompProjectCache.ContainsKey(f)).ToList
-        '获取前置 Mod 工程信息
+        '获取前置工程信息
         If UndoneDeps.Any Then
         If UndoneDeps.Any Then
-            Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置 Mod:{Join(UndoneDeps, ",")}")
+            Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置:{Join(UndoneDeps, ",")}")
             Dim Projects As JArray
             Dim Projects As JArray
             If TargetProject.FromCurseForge Then
             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
             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
             End If
             For Each Project In Projects
             For Each Project In Projects
-                Dim NewProject As New CompProject(Project) '在 New 的时候就添加了缓存
+                Dim Unused As New CompProject(Project) '在 New 的时候会添加缓存以便之后读取
             Next
             Next
         End If
         End If
-        '更新前置 Mod 信息
+        '更新前置信息
         If Deps.Any Then
         If Deps.Any Then
             For Each DepProject In Deps.Where(Function(id) CompProjectCache.ContainsKey(id)).Select(Function(id) CompProjectCache(id))
             For Each DepProject In Deps.Where(Function(id) CompProjectCache.ContainsKey(id)).Select(Function(id) CompProjectCache(id))
                 For Each File In CompFilesCache(ProjectId)
                 For Each File In CompFilesCache(ProjectId)
@@ -1518,28 +1659,28 @@ Retry:
     End Function
     End Function
 
 
     ''' <summary>
     ''' <summary>
-    ''' 预载包含大量 CompFile 的卡片,添加必要的元素和前置 Mod 列表。
+    ''' 预载包含大量 CompFile 的卡片,添加必要的元素和前置列表。
     ''' </summary>
     ''' </summary>
     Public Sub CompFilesCardPreload(Stack As StackPanel, Files As List(Of CompFile))
     Public Sub CompFilesCardPreload(Stack As StackPanel, Files As List(Of CompFile))
         '获取卡片对应的前置 ID
         '获取卡片对应的前置 ID
         '如果为整合包就不会有 Dependencies 信息,所以不用管
         '如果为整合包就不会有 Dependencies 信息,所以不用管
         Dim Deps As List(Of String) = Files.SelectMany(Function(f) f.Dependencies).Distinct.ToList()
         Dim Deps As List(Of String) = Files.SelectMany(Function(f) f.Dependencies).Distinct.ToList()
         Deps.Sort()
         Deps.Sort()
-        If Not Deps.Any() Then Exit Sub
+        If Not Deps.Any() Then Return
         Deps = Deps.Where(
         Deps = Deps.Where(
         Function(dep)
         Function(dep)
-            If Not CompProjectCache.ContainsKey(dep) Then Log($"[Comp] 未找到 ID {dep} 的前置 Mod 信息", LogLevel.Debug)
+            If Not CompProjectCache.ContainsKey(dep) Then Log($"[Comp] 未找到 ID {dep} 的前置信息", LogLevel.Debug)
             Return CompProjectCache.ContainsKey(dep)
             Return CompProjectCache.ContainsKey(dep)
         End Function).ToList
         End Function).ToList
         '添加开头间隔
         '添加开头间隔
-        Stack.Children.Add(New TextBlock With {.Text = "前置 Mod", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)})
-        '添加前置 Mod 列表
+        Stack.Children.Add(New TextBlock With {.Text = "前置资源", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)})
+        '添加前置列表
         For Each Dep In Deps
         For Each Dep In Deps
             Dim Item = CompProjectCache(Dep).ToCompItem(False, False)
             Dim Item = CompProjectCache(Dep).ToCompItem(False, False)
             Stack.Children.Add(Item)
             Stack.Children.Add(Item)
         Next
         Next
         '添加结尾间隔
         '添加结尾间隔
-        Stack.Children.Add(New TextBlock With {.Text = "可选版本", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)})
+        Stack.Children.Add(New TextBlock With {.Text = "版本列表", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)})
     End Sub
     End Sub
 
 
 #End Region
 #End Region

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

@@ -2,7 +2,9 @@
 
 
     '构造函数
     '构造函数
     Private TempFolder As String
     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()
         TempFolder = RequestTaskTempFolder()
         Directory.CreateDirectory(TempFolder & "Temp\")
         Directory.CreateDirectory(TempFolder & "Temp\")
@@ -71,11 +73,7 @@
         '将可能可用的日志文件导出
         '将可能可用的日志文件导出
         For Each FilePath In RightLogs
         For Each FilePath In RightLogs
             Try
             Try
-                If FilePath.Contains("crash-") Then
-                    AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(FilePath, ReadFile(FilePath).Split(vbCrLf.ToCharArray)))
-                Else
-                    AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(FilePath, ReadFile(FilePath, Encoding.UTF8).Split(vbCrLf.ToCharArray)))
-                End If
+                AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(FilePath, ReadFile(FilePath).Split(vbCrLf.ToCharArray)))
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "读取可能的崩溃日志文件失败(" & FilePath & ")")
                 Log(ex, "读取可能的崩溃日志文件失败(" & FilePath & ")")
             End Try
             End Try
@@ -117,11 +115,7 @@ Extracted:
                 If Not TargetFile.Exists OrElse TargetFile.Length = 0 Then Continue For
                 If Not TargetFile.Exists OrElse TargetFile.Length = 0 Then Continue For
                 Dim Ext As String = TargetFile.Extension.ToLower
                 Dim Ext As String = TargetFile.Extension.ToLower
                 If Ext = ".log" OrElse Ext = ".txt" Then
                 If Ext = ".log" OrElse Ext = ".txt" Then
-                    If TargetFile.Name.StartsWithF("crash-") Then
-                        AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName).Split(vbCrLf.ToCharArray)))
-                    Else
-                        AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName, Encoding.UTF8).Split(vbCrLf.ToCharArray)))
-                    End If
+                    AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName).Split(vbCrLf.ToCharArray)))
                 Else
                 Else
                     File.Delete(TargetFile.FullName)
                     File.Delete(TargetFile.FullName)
                 End If
                 End If
@@ -137,20 +131,21 @@ Extracted:
     Private Enum AnalyzeFileType
     Private Enum AnalyzeFileType
         HsErr
         HsErr
         MinecraftLog
         MinecraftLog
-        ExtraLog
+        ExtraLogFile
+        ExtraReportFile
         CrashReport
         CrashReport
     End Enum
     End Enum
     Private DirectFile As KeyValuePair(Of String, String())? = Nothing '在弹窗中选择直接打开的文件
     Private DirectFile As KeyValuePair(Of String, String())? = Nothing '在弹窗中选择直接打开的文件
     ''' <summary>
     ''' <summary>
     ''' 从 AnalyzeRawFiles 中提取实际有用的文本片段存储到 AnalyzeFiles,并整理可用于生成报告的文件。
     ''' 从 AnalyzeRawFiles 中提取实际有用的文本片段存储到 AnalyzeFiles,并整理可用于生成报告的文件。
-    ''' 返回找到的用于分析的项目数。
+    ''' 返回是否有足够信息可用于分析。
     ''' </summary>
     ''' </summary>
-    Public Function Prepare() As Integer
+    Public Function Prepare() As Boolean
         Log("[Crash] 步骤 2:准备日志文本")
         Log("[Crash] 步骤 2:准备日志文本")
 
 
         '对日志文件进行分类
         '对日志文件进行分类
         DirectFile = Nothing
         DirectFile = Nothing
-        Dim TotalFiles As New List(Of KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String())))
+        Dim AllFiles As New List(Of KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String())))
         For Each LogFile In AnalyzeRawFiles
         For Each LogFile In AnalyzeRawFiles
             Dim MatchName As String = GetFileNameFromPath(LogFile.Key).ToLower
             Dim MatchName As String = GetFileNameFromPath(LogFile.Key).ToLower
             Dim TargetType As AnalyzeFileType
             Dim TargetType As AnalyzeFileType
@@ -170,27 +165,35 @@ Extracted:
                     TargetType = AnalyzeFileType.MinecraftLog
                     TargetType = AnalyzeFileType.MinecraftLog
                     If DirectFile Is Nothing Then DirectFile = LogFile
                     If DirectFile Is Nothing Then DirectFile = LogFile
                 Else
                 Else
-                    TargetType = AnalyzeFileType.ExtraLog
+                    TargetType = AnalyzeFileType.ExtraLogFile
                 End If
                 End If
-            ElseIf MatchName.EndsWithF(".log", True) OrElse MatchName.EndsWithF(".txt", True) Then
-                TargetType = AnalyzeFileType.ExtraLog
+            ElseIf MatchName.EndsWithF(".log", True) Then
+                TargetType = AnalyzeFileType.ExtraLogFile
+            ElseIf MatchName.EndsWithF(".txt", True) Then
+                TargetType = AnalyzeFileType.ExtraReportFile
             Else
             Else
                 Log("[Crash] " & MatchName & " 分类为 Ignore")
                 Log("[Crash] " & MatchName & " 分类为 Ignore")
                 Continue For
                 Continue For
             End If
             End If
-            If Not LogFile.Value.Any() Then
-                Log("[Crash] " & MatchName & " 由于内容为空跳过")
-            Else
-                TotalFiles.Add(New KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))(TargetType, LogFile))
+            If LogFile.Value.Any Then
+                AllFiles.Add(New KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))(TargetType, LogFile))
                 Log("[Crash] " & MatchName & " 分类为 " & GetStringFromEnum(TargetType))
                 Log("[Crash] " & MatchName & " 分类为 " & GetStringFromEnum(TargetType))
+            Else
+                Log("[Crash] " & MatchName & " 由于内容为空跳过")
             End If
             End If
         Next
         Next
 
 
+        '若只有额外日志,则将它们视作 Minecraft 日志
+        If AllFiles.Any() AndAlso AllFiles.All(Function(p) p.Key = AnalyzeFileType.ExtraLogFile) Then
+            Log("[Crash] 由于仅发现了额外日志,将它们视作 Minecraft 日志进行分析")
+            AllFiles = AllFiles.Select(Function(p) New KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))(AnalyzeFileType.MinecraftLog, p.Value)).ToList
+        End If
+
         '将分类后的文件分别写入
         '将分类后的文件分别写入
-        For Each SelectType In {AnalyzeFileType.MinecraftLog, AnalyzeFileType.HsErr, AnalyzeFileType.ExtraLog, AnalyzeFileType.CrashReport}
+        For Each SelectType In {AnalyzeFileType.MinecraftLog, AnalyzeFileType.HsErr, AnalyzeFileType.ExtraLogFile, AnalyzeFileType.CrashReport}
             '获取该种类的所有文件 {文件路径 -> 文件内容行}
             '获取该种类的所有文件 {文件路径 -> 文件内容行}
             Dim SelectedFiles As New List(Of KeyValuePair(Of String, String()))
             Dim SelectedFiles As New List(Of KeyValuePair(Of String, String()))
-            For Each File In TotalFiles
+            For Each File In AllFiles
                 If SelectType = File.Key Then SelectedFiles.Add(File.Value)
                 If SelectType = File.Key Then SelectedFiles.Add(File.Value)
             Next
             Next
             If Not SelectedFiles.Any() Then Continue For
             If Not SelectedFiles.Any() Then Continue For
@@ -232,54 +235,59 @@ Extracted:
                         Next
                         Next
                         '选择一份最佳的来自启动器的游戏日志
                         '选择一份最佳的来自启动器的游戏日志
                         For Each FileName As String In {"rawoutput.log", "启动器日志.txt", "log1.txt", "游戏崩溃前的输出.txt", "PCL2 启动器日志.txt", "PCL 启动器日志.txt"}
                         For Each FileName As String In {"rawoutput.log", "启动器日志.txt", "log1.txt", "游戏崩溃前的输出.txt", "PCL2 启动器日志.txt", "PCL 启动器日志.txt"}
-                            If FileNameDict.ContainsKey(FileName) Then
-                                Dim CurrentLog = FileNameDict(FileName)
-                                '截取 “以下为游戏输出的最后一段内容” 后的内容
-                                Dim HasLauncherMark As Boolean = False
-                                For Each Line In CurrentLog.Value
-                                    If HasLauncherMark Then
-                                        LogMc += Line & vbLf
-                                    ElseIf Line.Contains("以下为游戏输出的最后一段内容") Then
-                                        HasLauncherMark = True
-                                        Log("[Crash] 找到 PCL 输出的游戏实时日志头")
-                                    End If
-                                Next
-                                '导入后 500 行
-                                If Not HasLauncherMark Then LogMc += GetHeadTailLines(CurrentLog.Value, 0, 500)
-                                LogMc = LogMc.TrimEnd(vbCrLf.ToCharArray)
-                                Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为启动器日志")
-                                Exit For
-                            End If
+                            If Not FileNameDict.ContainsKey(FileName) Then Continue For
+                            Dim CurrentLog = FileNameDict(FileName)
+                            '截取 “以下为游戏输出的最后一段内容” 后的内容
+                            Dim HasLauncherMark As Boolean = False
+                            For Each Line In CurrentLog.Value
+                                If HasLauncherMark Then
+                                    LogMc += Line & vbLf
+                                ElseIf Line.Contains("以下为游戏输出的最后一段内容") Then
+                                    HasLauncherMark = True
+                                    Log("[Crash] 找到 PCL 输出的游戏实时日志头")
+                                End If
+                            Next
+                            '导入后 500 行
+                            If Not HasLauncherMark Then LogMc += GetHeadTailLines(CurrentLog.Value, 0, 500)
+                            LogMc = LogMc.TrimEnd(vbCrLf.ToCharArray)
+                            Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为启动器日志")
+                            Exit For
                         Next
                         Next
                         '选择一份最佳的 Minecraft Log
                         '选择一份最佳的 Minecraft Log
                         For Each FileName As String In {"latest.log", "latest log.txt", "debug.log", "debug log.txt"}
                         For Each FileName As String In {"latest.log", "latest log.txt", "debug.log", "debug log.txt"}
-                            If FileNameDict.ContainsKey(FileName) Then
-                                Dim CurrentLog = FileNameDict(FileName)
-                                LogMc += GetHeadTailLines(CurrentLog.Value, 250, 500)
-                                Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft 日志")
-                                Exit For
-                            End If
+                            If Not FileNameDict.ContainsKey(FileName) Then Continue For
+                            Dim CurrentLog = FileNameDict(FileName)
+                            LogMc += GetHeadTailLines(CurrentLog.Value, 1500, 500)
+                            Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft 日志")
+                            Exit For
                         Next
                         Next
                         '查找 Debug Log
                         '查找 Debug Log
                         For Each FileName As String In {"debug.log", "debug log.txt"}
                         For Each FileName As String In {"debug.log", "debug log.txt"}
-                            If FileNameDict.ContainsKey(FileName) Then
-                                Dim CurrentLog = FileNameDict(FileName)
-                                LogMcDebug += GetHeadTailLines(CurrentLog.Value, 1000, 0)
-                                Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft Debug 日志")
-                                Exit For
-                            End If
+                            If Not FileNameDict.ContainsKey(FileName) Then Continue For
+                            Dim CurrentLog = FileNameDict(FileName)
+                            LogMcDebug += GetHeadTailLines(CurrentLog.Value, 1000, 0)
+                            Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft Debug 日志")
+                            Exit For
                         Next
                         Next
-                        '检查错误
+                        '兜底
                         If LogMc = "" Then
                         If LogMc = "" Then
-                            LogMc = Nothing
-                            Throw New Exception("无法找到匹配的 Minecraft Log")
+                            If LogMcDebug <> "" Then '如果没有找到 Minecraft 日志,则使用 Debug 日志作为兜底
+                                LogMc = LogMcDebug
+                            ElseIf FileNameDict.Any() Then '如果都没有找到,则使用第一个文件
+                                Dim CurrentLog = FileNameDict.First.Value
+                                LogMc += GetHeadTailLines(CurrentLog.Value, 1500, 500)
+                                Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为兜底日志")
+                            Else
+                                LogMc = Nothing
+                                Throw New Exception("无法找到匹配的 Minecraft Log")
+                            End If
                         End If
                         End If
                         If LogMcDebug = "" Then LogMcDebug = Nothing
                         If LogMcDebug = "" Then LogMcDebug = Nothing
-                    Case AnalyzeFileType.ExtraLog
+                    Case AnalyzeFileType.ExtraLogFile, AnalyzeFileType.ExtraReportFile
                         '全部丢过去
                         '全部丢过去
                         For Each SelectedFile In SelectedFiles
                         For Each SelectedFile In SelectedFiles
                             OutputFiles.Add(SelectedFile.Key)
                             OutputFiles.Add(SelectedFile.Key)
-                            Log("[Crash] 输出报告:" & SelectedFile.Key & ",作为额外日志")
+                            Log("[Crash] 输出报告:" & SelectedFile.Key & ",不用作分析")
                         Next
                         Next
                 End Select
                 End Select
             Catch ex As Exception
             Catch ex As Exception
@@ -287,15 +295,13 @@ Extracted:
             End Try
             End Try
         Next
         Next
 
 
-        '获取种类数
-        Dim ResultCount As Integer = If(LogMc Is Nothing, 0, 1) + If(LogHs Is Nothing, 0, 1) + If(LogCrash Is Nothing, 0, 1)
-        If ResultCount = 0 Then
-            Log("[Crash] 步骤 2:准备日志文本完成,没有任何可供分析的日志")
-        Else
+        '结束
+        Prepare = LogMc IsNot Nothing OrElse LogHs IsNot Nothing OrElse LogCrash IsNot Nothing
+        If Prepare Then
             Log(("[Crash] 步骤 2:准备日志文本完成,找到" & If(LogMc Is Nothing, "", "游戏日志、") & If(LogMcDebug Is Nothing, "", "游戏 Debug 日志、") & If(LogHs Is Nothing, "", "虚拟机日志、") & If(LogCrash Is Nothing, "", "崩溃日志、")).TrimEnd("、") & "用作分析")
             Log(("[Crash] 步骤 2:准备日志文本完成,找到" & If(LogMc Is Nothing, "", "游戏日志、") & If(LogMcDebug Is Nothing, "", "游戏 Debug 日志、") & If(LogHs Is Nothing, "", "虚拟机日志、") & If(LogCrash Is Nothing, "", "崩溃日志、")).TrimEnd("、") & "用作分析")
+        Else
+            Log("[Crash] 步骤 2:准备日志文本完成,没有任何可供分析的日志")
         End If
         End If
-        Return ResultCount
-
     End Function
     End Function
     ''' <summary>
     ''' <summary>
     ''' 输出字符串的前后某些行,并统一行尾为 vbLf (正则 \n)、删除空行和重复行。
     ''' 输出字符串的前后某些行,并统一行尾为 vbLf (正则 \n)、删除空行和重复行。
@@ -335,6 +341,7 @@ Extracted:
     ''' 导致崩溃的原因枚举。
     ''' 导致崩溃的原因枚举。
     ''' </summary>
     ''' </summary>
     Private Enum CrashReason
     Private Enum CrashReason
+        Java虚拟机参数有误
         Mod文件被解压
         Mod文件被解压
         MixinBootstrap缺失
         MixinBootstrap缺失
         内存不足
         内存不足
@@ -386,7 +393,7 @@ Extracted:
     ''' </summary>
     ''' </summary>
     Public Sub Analyze(Optional Version As McVersion = Nothing)
     Public Sub Analyze(Optional Version As McVersion = Nothing)
         Log("[Crash] 步骤 3:分析崩溃原因")
         Log("[Crash] 步骤 3:分析崩溃原因")
-        LogAll = (If(LogMc, "") & If(LogMcDebug, "") & If(LogHs, "") & If(LogCrash, ""))
+        LogAll = If(LogMc, If(LogMcDebug, "")) & If(LogHs, "") & If(LogCrash, "")
 
 
         '1. 精准日志匹配,中/高优先级
         '1. 精准日志匹配,中/高优先级
         AnalyzeCrit1()
         AnalyzeCrit1()
@@ -472,16 +479,18 @@ Done:
         '空白分析
         '空白分析
         If LogMc Is Nothing AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing Then
         If LogMc Is Nothing AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing Then
             AppendReason(CrashReason.没有可用的分析文件)
             AppendReason(CrashReason.没有可用的分析文件)
-            Exit Sub
+            Return
         End If
         End If
 
 
         '崩溃报告分析,高优先级
         '崩溃报告分析,高优先级
         If LogCrash IsNot Nothing Then
         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("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)}) '#7071
         End If
         End If
 
 
         '游戏日志分析
         '游戏日志分析
         If LogMc IsNot Nothing Then
         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("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("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)
             If LogMc.Contains("java.lang.ClassCastException: java.base/jdk") Then AppendReason(CrashReason.使用JDK)
@@ -504,13 +513,13 @@ Done:
             If LogMc.Contains("Couldn't set pixel format") Then AppendReason(CrashReason.显卡驱动不支持导致无法设置像素格式)
             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.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.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("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("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("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("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("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("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 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名称包含特殊字符)
             If LogMc.Contains("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符)
@@ -533,8 +542,8 @@ Done:
             If LogMc.Contains("Found a duplicate mod") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "Found a duplicate mod[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
             If LogMc.Contains("Found a duplicate mod") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "Found a duplicate mod[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
             If LogMc.Contains("Found duplicate mods") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=Mod ID: ')\w+?(?=' from mod files:)").Distinct.ToList)
             If LogMc.Contains("Found duplicate mods") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=Mod ID: ')\w+?(?=' from mod files:)").Distinct.ToList)
             If LogMc.Contains("ModResolutionException: Duplicate") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "ModResolutionException: Duplicate[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
             If LogMc.Contains("ModResolutionException: Duplicate") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "ModResolutionException: Duplicate[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
-            If LogMc.Contains("Incompatible mods found!") Then '#5006
-                AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), ""))
+            If LogMc.Contains("Incompatible mods found!") Then '#5006, #5115
+                AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), "").BeforeFirst("更多信息:").Replace("Some of your mods are incompatible with the game or each other!", "").Trim((vbCrLf & " ").ToCharArray))
             End If
             End If
             If LogMc.Contains("Missing or unsupported mandatory dependencies:") Then
             If LogMc.Contains("Missing or unsupported mandatory dependencies:") Then
                 AppendReason(CrashReason.Mod缺少前置或MC版本错误,
                 AppendReason(CrashReason.Mod缺少前置或MC版本错误,
@@ -614,9 +623,9 @@ Done:
             Dim IsMixin As Boolean = MixinAnalyze(LogMc)
             Dim IsMixin As Boolean = MixinAnalyze(LogMc)
             '常规信息
             '常规信息
             If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf))
             If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf))
-            If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf))
-            If LogMc.Contains("A potential solution has been determined, this may resolve your problem:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined, this may resolve your problem:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf))
-            If LogMc.Contains("确定了一种可能的解决方法,这样做可能会解决你的问题:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=确定了一种可能的解决方法,这样做可能会解决你的问题:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf))
+            If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)(\s+ - [^\n]+\n)+"), ""), "(?<=\s+)[^\n]+"), vbLf))
+            If LogMc.Contains("A potential solution has been determined, this may resolve your problem:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined, this may resolve your problem:\n)(\s+ - [^\n]+\n)+"), ""), "(?<=\s+)[^\n]+"), vbLf))
+            If LogMc.Contains("确定了一种可能的解决方法,这样做可能会解决你的问题:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=确定了一种可能的解决方法,这样做可能会解决你的问题:\n)(\s+ - [^\n]+\n)+"), ""), "(?<=\s+)[^\n]+"), vbLf))
             If Not IsMixin AndAlso LogMc.Contains("due to errors, provided by ") Then '在 #3104 的情况下,这一句导致 OptiFabric 的 Mixin 失败错判为 Fabric Loader 加载失败
             If Not IsMixin AndAlso LogMc.Contains("due to errors, provided by ") Then '在 #3104 的情况下,这一句导致 OptiFabric 的 Mixin 失败错判为 Fabric Loader 加载失败
                 AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=due to errors, provided by ')[^']+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
                 AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=due to errors, provided by ')[^']+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
             End If
             End If
@@ -650,7 +659,7 @@ Done:
             End If
             End If
             'Mod 解析错误(常见于 Fabric 前置校验失败)
             'Mod 解析错误(常见于 Fabric 前置校验失败)
             If LogMc.Contains("Mod resolution failed") Then AppendReason(CrashReason.Mod加载器报错)
             If LogMc.Contains("Mod resolution failed") Then AppendReason(CrashReason.Mod加载器报错)
-            'Mixin 失败可以导致大量 Mod 实例创建失败
+            'Mixin 失败可以导致大量 Mod 实例创建失败,因此需要放到低优先级中
             If LogMc.Contains("Failed to create mod instance.") Then AppendReason(CrashReason.Mod初始化失败, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModID: )[^,]+"), If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModId )[^\n]+(?= for )"), "")).TrimEnd(vbCrLf)))
             If LogMc.Contains("Failed to create mod instance.") Then AppendReason(CrashReason.Mod初始化失败, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModID: )[^,]+"), If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModId )[^\n]+(?= for )"), "")).TrimEnd(vbCrLf)))
             '注意:Fabric 的 Warnings were found! 不一定是崩溃原因,它可能是单纯的警报
             '注意:Fabric 的 Warnings were found! 不一定是崩溃原因,它可能是单纯的警报
         End If
         End If
@@ -704,11 +713,12 @@ NextStack:
                 If Word.Length <= 2 OrElse Word.StartsWithF("func_") Then Continue For
                 If Word.Length <= 2 OrElse Word.StartsWithF("func_") Then Continue For
                 If {"com", "org", "net", "asm", "fml", "mod", "jar", "sun", "lib", "map", "gui", "dev", "nio", "api", "dsi", "top", "mcp",
                 If {"com", "org", "net", "asm", "fml", "mod", "jar", "sun", "lib", "map", "gui", "dev", "nio", "api", "dsi", "top", "mcp",
                     "core", "init", "mods", "main", "file", "game", "load", "read", "done", "util", "tile", "item", "base", "oshi", "impl", "data", "pool", "task",
                     "core", "init", "mods", "main", "file", "game", "load", "read", "done", "util", "tile", "item", "base", "oshi", "impl", "data", "pool", "task",
-                    "forge", "setup", "block", "model", "mixin", "event", "unimi", "netty", "world",
+                    "forge", "setup", "block", "model", "mixin", "event", "unimi", "netty", "world", "lwjgl",
                     "gitlab", "common", "server", "config", "mixins", "compat", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe", "render", "packet", "events",
                     "gitlab", "common", "server", "config", "mixins", "compat", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe", "render", "packet", "events",
                     "preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules", "service",
                     "preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules", "service",
                     "fastutil", "optifine", "internal", "platform", "override", "fabricmc", "neoforge",
                     "fastutil", "optifine", "internal", "platform", "override", "fabricmc", "neoforge",
-                    "injection", "listeners", "scheduler", "minecraft", "transformer", "transformers", "neoforged", "universal", "multipart", "minecraftforge", "blockentity", "spongepowered", "electronwill"
+                    "injection", "listeners", "scheduler", "minecraft", "universal", "multipart", "neoforged", "microsoft",
+                    "transformer", "transformers", "minecraftforge", "blockentity", "spongepowered", "electronwill", "concurrent"
                    }.Contains(Word.ToLower) Then Continue For
                    }.Contains(Word.ToLower) Then Continue For
                 PossibleWords.Add(Word.Trim)
                 PossibleWords.Add(Word.Trim)
             Next
             Next
@@ -851,20 +861,21 @@ NextStack:
     ''' <summary>
     ''' <summary>
     ''' 弹出崩溃弹窗,并指导导出崩溃报告。
     ''' 弹出崩溃弹窗,并指导导出崩溃报告。
     ''' </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()
         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()
             Sub()
                 '弹窗选择:查看日志
                 '弹窗选择:查看日志
                 If File.Exists(DirectFile.Value.Key) Then
                 If File.Exists(DirectFile.Value.Key) Then
-                    ShellOnly(DirectFile.Value.Key)
+                    StartProcess(DirectFile.Value.Key)
                 Else
                 Else
                     Dim FilePath As String = PathTemp & "Crash.txt"
                     Dim FilePath As String = PathTemp & "Crash.txt"
                     WriteFile(FilePath, Join(DirectFile.Value.Value, vbCrLf))
                     WriteFile(FilePath, Join(DirectFile.Value.Value, vbCrLf))
-                    ShellOnly(FilePath)
+                    StartProcess(FilePath)
                 End If
                 End If
             End Sub))
             End Sub))
             Case 3
             Case 3
@@ -873,7 +884,7 @@ NextStack:
                 Try
                 Try
                     '获取文件路径
                     '获取文件路径
                     RunInUiWait(Sub() FileAddress = SelectSaveFile("选择保存位置", "错误报告-" & Date.Now.ToString("G").Replace("/", "-").Replace(":", ".").Replace(" ", "_") & ".zip", "Minecraft 错误报告(*.zip)|*.zip"))
                     RunInUiWait(Sub() FileAddress = SelectSaveFile("选择保存位置", "错误报告-" & Date.Now.ToString("G").Replace("/", "-").Replace(":", ".").Replace(" ", "_") & ".zip", "Minecraft 错误报告(*.zip)|*.zip"))
-                    If String.IsNullOrEmpty(FileAddress) Then Exit Sub
+                    If String.IsNullOrEmpty(FileAddress) Then Return
                     Directory.CreateDirectory(GetPathFromFullPath(FileAddress))
                     Directory.CreateDirectory(GetPathFromFullPath(FileAddress))
                     If File.Exists(FileAddress) Then File.Delete(FileAddress)
                     If File.Exists(FileAddress) Then File.Delete(FileAddress)
                     '输出诊断信息
                     '输出诊断信息
@@ -896,9 +907,10 @@ NextStack:
                         End Select
                         End Select
                         If File.Exists(OutputFile) Then
                         If File.Exists(OutputFile) Then
                             If FileEncoding Is Nothing Then FileEncoding = GetEncoding(ReadFileBytes(OutputFile))
                             If FileEncoding Is Nothing Then FileEncoding = GetEncoding(ReadFileBytes(OutputFile))
-                            WriteFile(TempFolder & "Report\" & FileName,
-                                      SecretFilter(ReadFile(OutputFile, FileEncoding), If(FileName = "启动脚本.bat", "F", "*")),
-                                      Encoding:=FileEncoding)
+                            Dim FileContent As String = ReadFile(OutputFile, FileEncoding)
+                            FileContent = FilterAccessToken(FileContent, If(FileName = "启动脚本.bat", "F", "*"))
+                            FileContent = FilterUserName(FileContent, "*")
+                            WriteFile(TempFolder & "Report\" & FileName, FileContent, Encoding:=FileEncoding)
                             Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}")
                             Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}")
                         End If
                         End If
                     Next
                     Next
@@ -908,10 +920,15 @@ NextStack:
                     Hint("错误报告已导出!", HintType.Finish)
                     Hint("错误报告已导出!", HintType.Finish)
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "导出错误报告失败", LogLevel.Feedback)
                     Log(ex, "导出错误报告失败", LogLevel.Feedback)
-                    Exit Sub
+                    Return
                 End Try
                 End Try
                 OpenExplorer(FileAddress)
                 OpenExplorer(FileAddress)
         End Select
         End Select
+        '上报
+        If IsManualAnalyze Then Return
+        Telemetry("Minecraft 崩溃",
+                  "Version", If(TargetVersion Is Nothing, "null", TargetVersion.Version.ToString),
+                  "Detail", FilterUserName(Detail, "*"))
     End Sub
     End Sub
     ''' <summary>
     ''' <summary>
     ''' 获取崩溃分析的结果描述。
     ''' 获取崩溃分析的结果描述。
@@ -932,12 +949,14 @@ NextStack:
         For Each Reason In CrashReasons
         For Each Reason In CrashReasons
             Dim Additional As List(Of String) = Reason.Value
             Dim Additional As List(Of String) = Reason.Value
             Select Case Reason.Key
             Select Case Reason.Key
+                Case CrashReason.Java虚拟机参数有误
+                    Results.Add("由于 Java 参数有误,导致游戏无法继续运行。\n请检查高级选项中的 Java 虚拟机参数和游戏参数设置是否有误。")
                 Case CrashReason.Mod文件被解压
                 Case CrashReason.Mod文件被解压
                     Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。")
                     Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。")
                 Case CrashReason.内存不足
                 Case CrashReason.内存不足
                     Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是因为电脑内存不足、游戏分配的内存不足,或是配置要求过高。\n\n你可以尝试在 更多 → 百宝箱 中选择 内存优化,然后再启动游戏。\n如果还是不行,请在启动设置中增加为游戏分配的内存,并删除配置要求较高的材质、Mod、光影。\n如果依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h")
                     Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是因为电脑内存不足、游戏分配的内存不足,或是配置要求过高。\n\n你可以尝试在 更多 → 百宝箱 中选择 内存优化,然后再启动游戏。\n如果还是不行,请在启动设置中增加为游戏分配的内存,并删除配置要求较高的材质、Mod、光影。\n如果依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h")
                 Case CrashReason.使用OpenJ9
                 Case CrashReason.使用OpenJ9
-                    Results.Add("游戏因为使用 Open J9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。")
+                    Results.Add("游戏因为使用 OpenJ9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。")
                 Case CrashReason.使用JDK
                 Case CrashReason.使用JDK
                     Results.Add("游戏似乎因为使用 JDK,或 Java 版本过高而崩溃了。\n请在启动设置的 Java 选择一项中改用 JRE 8(Java 8),然后再启动游戏。\n如果你没有安装 JRE 8,你可以从网络中下载、安装一个。")
                     Results.Add("游戏似乎因为使用 JDK,或 Java 版本过高而崩溃了。\n请在启动设置的 Java 选择一项中改用 JRE 8(Java 8),然后再启动游戏。\n如果你没有安装 JRE 8,你可以从网络中下载、安装一个。")
                 Case CrashReason.Java版本过高
                 Case CrashReason.Java版本过高
@@ -1012,14 +1031,22 @@ NextStack:
                     End If
                     End If
                 Case CrashReason.特定实体导致崩溃
                 Case CrashReason.特定实体导致崩溃
                     If Additional.Count = 1 Then
                     If Additional.Count = 1 Then
-                        Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况:\n - 若正常运行,则是该实体导致出错,你或许需要使用一些方式删除此实体。\n - 若仍然出错,问题就可能来自其他原因……\h")
+                        If Additional.First.Contains("minecraft:player") Then
+                            If Additional.First.Contains(" ") Then
+                                Results.Add($"游戏因为位于 {Additional.First.AfterFirst(" ")} 的玩家实体导致了崩溃。\h")
+                            Else
+                                Results.Add($"游戏因为玩家实体导致了崩溃。\h")
+                            End If
+                        Else
+                            Results.Add($"游戏因为实体 {Additional.First} 导致了崩溃。\h")
+                        End If
                     Else
                     Else
-                        Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,观察游戏的运行情况:\n - 若正常运行,则是某些实体导致出错,你或许需要删除该世界。\n - 若仍然出错,问题就可能来自其他原因……\h")
+                        Results.Add($"游戏因为下列实体导致了崩溃:{Additional.Join("、")}\h")
                     End If
                     End If
                 Case CrashReason.OptiFine与Forge不兼容
                 Case CrashReason.OptiFine与Forge不兼容
                     Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")
                     Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")
                 Case CrashReason.ShadersMod与OptiFine同时安装
                 Case CrashReason.ShadersMod与OptiFine同时安装
-                    Results.Add("无需同时安装 Optifine 和 Shaders Mod,OptiFine 已经集成了 Shaders Mod 的功能。\n在删除 Shaders Mod 后,游戏即可正常运行。")
+                    Results.Add("无需同时安装 OptiFine 和 Shaders Mod,OptiFine 已经集成了 Shaders Mod 的功能。\n在删除 Shaders Mod 后,游戏即可正常运行。")
                 Case CrashReason.低版本Forge与高版本Java不兼容
                 Case CrashReason.低版本Forge与高版本Java不兼容
                     Results.Add("由于低版本 Forge 与当前 Java 不兼容,导致了游戏崩溃。\n\n请尝试以下解决方案:\n - 更新 Forge 到 36.2.26 或更高版本\n - 换用版本低于 1.8.0.320 的 Java")
                     Results.Add("由于低版本 Forge 与当前 Java 不兼容,导致了游戏崩溃。\n\n请尝试以下解决方案:\n - 更新 Forge 到 36.2.26 或更高版本\n - 换用版本低于 1.8.0.320 的 Java")
                 Case CrashReason.版本Json中存在多个Forge
                 Case CrashReason.版本Json中存在多个Forge
@@ -1096,7 +1123,7 @@ NextStack:
                 If(Not Results.Any(Function(r) r.EndsWithF("\h")) OrElse IsHandAnalyze, "",
                 If(Not Results.Any(Function(r) r.EndsWithF("\h")) OrElse IsHandAnalyze, "",
                     vbCrLf & "如果要寻求帮助,请把错误报告文件发给对方,而不是发送这个窗口的照片或者截图。" &
                     vbCrLf & "如果要寻求帮助,请把错误报告文件发给对方,而不是发送这个窗口的照片或者截图。" &
                     If(If(PageSetupSystem.IsLauncherNewest(), True), "",
                     If(If(PageSetupSystem.IsLauncherNewest(), True), "",
-                    vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 设置 → 启动器 → 检查更新 来更新 PCL。"))
+                    vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 [设置 → 其他 → 启动器 → 检查更新] 更新 PCL。"))
     End Function
     End Function
 
 
 End Class
 End Class

+ 128 - 117
Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb

@@ -197,10 +197,17 @@
     Public DlClientListMojangLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain)
     Public DlClientListMojangLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain)
     Private IsNewClientVersionHinted As Boolean = False
     Private IsNewClientVersionHinted As Boolean = False
     Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult))
     Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult))
-        Dim Json As JObject = NetGetCodeByRequestRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", IsJson:=True)
+        Dim StartTime As Long = GetTimeTick()
+        Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", RequireJson:=True))
         Try
         Try
             Dim Versions As JArray = Json("versions")
             Dim Versions As JArray = Json("versions")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
+            '确定官方源是否可用
+            If Not DlPreferMojang Then
+                Dim DeltaTime = GetTimeTick() - StartTime
+                DlPreferMojang = DeltaTime < 4000
+                Log($"[Download] Mojang 官方源加载耗时:{DeltaTime}ms,{If(DlPreferMojang, "可优先使用官方源", "不优先使用官方源")}")
+            End If
             '添加 PCL 特供项
             '添加 PCL 特供项
             If File.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(GetJson(ReadFile(PathTemp & "Cache\download.json")))
             If File.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(GetJson(ReadFile(PathTemp & "Cache\download.json")))
             '返回
             '返回
@@ -228,7 +235,7 @@
     ''' </summary>
     ''' </summary>
     Public DlClientListBmclapiLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
     Public DlClientListBmclapiLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
     Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of String, DlClientListResult))
     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", RequireJson:=True))
         Try
         Try
             Dim Versions As JArray = Json("versions")
             Dim Versions As JArray = Json("versions")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
@@ -237,7 +244,7 @@
             '检查是否有要求的版本(#5195)
             '检查是否有要求的版本(#5195)
             If Not String.IsNullOrEmpty(Loader.Input) Then
             If Not String.IsNullOrEmpty(Loader.Input) Then
                 Dim Id = Loader.Input
                 Dim Id = Loader.Input
-                If Not DlClientListLoader.Output.Value("versions").Any(Function(v) v("id") = Id) Then
+                If DlClientListLoader.Output.Value IsNot Nothing AndAlso Not DlClientListLoader.Output.Value("versions").Any(Function(v) v("id") = Id) Then
                     Throw New Exception("BMCLAPI 源未包含目标版本 " & Id)
                     Throw New Exception("BMCLAPI 源未包含目标版本 " & Id)
                 End If
                 End If
             End If
             End If
@@ -370,7 +377,7 @@
     ''' </summary>
     ''' </summary>
     Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain)
     Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain)
     Private Sub DlOptiFineListOfficialMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
     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 & ")")
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         Try
         Try
             '获取所有版本信息
             '获取所有版本信息
@@ -406,7 +413,7 @@
     ''' </summary>
     ''' </summary>
     Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain)
     Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain)
     Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
     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", RequireJson:=True))
         Try
         Try
             Dim Versions As New List(Of DlOptiFineListEntry)
             Dim Versions As New List(Of DlOptiFineListEntry)
             For Each Token As JObject In Json
             For Each Token As JObject In Json
@@ -476,7 +483,7 @@
     ''' </summary>
     ''' </summary>
     Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain)
     Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain)
     Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
     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", SimulateBrowserHeaders:=True)
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         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)")
         Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)")
@@ -490,7 +497,7 @@
     ''' </summary>
     ''' </summary>
     Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain)
     Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain)
     Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
     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 & ")")
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         '获取所有版本信息
         '获取所有版本信息
         Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?")
         Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?")
@@ -587,7 +594,7 @@
             VersionName = Version
             VersionName = Version
             Me.Version = New Version(Version)
             Me.Version = New Version(Version)
             Me.Inherit = Inherit
             Me.Inherit = Inherit
-            FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch)
+            FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch)
         End Sub
         End Sub
     End Class
     End Class
 
 
@@ -622,11 +629,11 @@
     Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
     Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
         Dim Result As String
         Dim Result As String
         Try
         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
                                           Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057
-                                          ".html", UseBrowserUserAgent:=True)
+                                          ".html", SimulateBrowserHeaders:=True)
         Catch ex As Exception
         Catch ex As Exception
-            If GetExceptionSummary(ex).Contains("(404)") Then
+            If ex.GetBrief().Contains("(404)") Then
                 Throw New Exception("不可用")
                 Throw New Exception("不可用")
             Else
             Else
                 Throw
                 Throw
@@ -693,9 +700,8 @@
     ''' Forge 版本列表,BMCLAPI。
     ''' Forge 版本列表,BMCLAPI。
     ''' </summary>
     ''' </summary>
     Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
     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("-", "_"), RequireJson:=True)) '兼容 Forge 1.7.10-pre4,#4057
         Dim Versions As New List(Of DlForgeVersionEntry)
         Dim Versions As New List(Of DlForgeVersionEntry)
         Try
         Try
             Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input)
             Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input)
@@ -829,8 +835,8 @@
     Public DlNeoForgeListOfficialLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Official", AddressOf DlNeoForgeListOfficialMain)
     Public DlNeoForgeListOfficialLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Official", AddressOf DlNeoForgeListOfficialMain)
     Private Sub DlNeoForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
     Private Sub DlNeoForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
         '获取版本列表 JSON
         '获取版本列表 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", RequireJson:=True)
+        Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", RequireJson:=True)
         If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
         If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
         '解析
         '解析
         Try
         Try
@@ -847,8 +853,8 @@
     Public DlNeoForgeListBmclapiLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Bmclapi", AddressOf DlNeoForgeListBmclapiMain)
     Public DlNeoForgeListBmclapiLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Bmclapi", AddressOf DlNeoForgeListBmclapiMain)
     Public Sub DlNeoForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
     Public Sub DlNeoForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
         '获取版本列表 JSON
         '获取版本列表 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", RequireJson:=True)
+        Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", RequireJson:=True)
         If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
         If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
         '解析
         '解析
         Try
         Try
@@ -953,7 +959,7 @@
     ''' </summary>
     ''' </summary>
     Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain)
     Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain)
     Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
     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", RequireJson:=True))
         Try
         Try
             Dim Json As JObject = Result("versions")
             Dim Json As JObject = Result("versions")
             Dim Versions As New List(Of DlLiteLoaderListEntry)
             Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -981,7 +987,7 @@
     ''' </summary>
     ''' </summary>
     Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain)
     Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain)
     Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
     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", RequireJson:=True))
         Try
         Try
             Dim Json As JObject = Result("versions")
             Dim Json As JObject = Result("versions")
             Dim Versions As New List(Of DlLiteLoaderListEntry)
             Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -1052,7 +1058,7 @@
     ''' </summary>
     ''' </summary>
     Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain)
     Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain)
     Private Sub DlFabricListOfficialMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
     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", RequireJson:=True))
         Try
         Try
             Dim Output = New DlFabricListResult With {.IsOfficial = True, .SourceName = "Fabric 官方源", .Value = Result}
             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("获取到的列表缺乏必要项")
             If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -1067,7 +1073,7 @@
     ''' </summary>
     ''' </summary>
     Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain)
     Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain)
     Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
     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", RequireJson:=True))
         Try
         Try
             Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result}
             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("获取到的列表缺乏必要项")
             If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -1094,72 +1100,37 @@
 #Region "DlMod | Mod 镜像源请求"
 #Region "DlMod | Mod 镜像源请求"
 
 
     ''' <summary>
     ''' <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,会进行重试。
     ''' 调用 NetRequest,会进行重试。
     ''' </summary>
     ''' </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))
         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 = ""
         Dim Exs As String = ""
         For Each Source In Urls
         For Each Source In Urls
             Try
             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, RequireJson:=True))
             Catch ex As Exception
             Catch ex As Exception
                 Exs += ex.Message + vbCrLf
                 Exs += ex.Message + vbCrLf
             End Try
             End Try
@@ -1171,18 +1142,54 @@
 
 
 #Region "DlSource | 镜像下载源"
 #Region "DlSource | 镜像下载源"
 
 
-    Public Function DlSourceResourceGet(Original As String) As String()
+    Private DlPreferMojang As Boolean = False
+    ''' <summary>
+    ''' 下载文件(而非获取版本列表)的时候,是否优先使用官方源。
+    ''' </summary>
+    Public ReadOnly Property DlSourcePreferMojang As Boolean
+        Get
+            Return Setup.Get("ToolDownloadSource") = 2 OrElse (Setup.Get("ToolDownloadSource") = 1 AndAlso DlPreferMojang)
+        End Get
+    End Property
+    ''' <summary>
+    ''' 下载文件(而非获取版本列表)的时候,根据是否优先使用官方源决定使用 Url 的顺序。
+    ''' </summary>
+    Public Function DlSourceOrder(OfficialUrls As IEnumerable(Of String), MirrorUrls As IEnumerable(Of String)) As IEnumerable(Of String)
+        Return If(DlSourcePreferMojang, OfficialUrls.Union(MirrorUrls), MirrorUrls.Union(OfficialUrls))
+    End Function
+    ''' <summary>
+    ''' 获取版本列表(而非下载文件)的时候,是否优先使用官方源。
+    ''' </summary>
+    Public ReadOnly Property DlVersionListPreferMojang As Boolean
+        Get
+            Return Setup.Get("ToolDownloadVersion") = 2 OrElse (Setup.Get("ToolDownloadVersion") = 1 AndAlso DlPreferMojang)
+        End Get
+    End Property
+    ''' <summary>
+    ''' 获取版本列表(而非下载文件)的时候,根据是否优先使用官方源决定使用 Url 的顺序。
+    ''' </summary>
+    Public Function DlVersionListOrder(OfficialUrls As IEnumerable(Of String), MirrorUrls As IEnumerable(Of String)) As IEnumerable(Of String)
+        Return If(DlVersionListPreferMojang, OfficialUrls.Union(MirrorUrls), MirrorUrls.Union(OfficialUrls))
+    End Function
+
+
+    ''' <summary>
+    ''' 下载 Assets 文件。
+    ''' </summary>
+    Public Function DlSourceAssetsGet(Original As String) As IEnumerable(Of String)
         Original = Original.Replace("http://resources.download.minecraft.net", "https://resources.download.minecraft.net")
         Original = Original.Replace("http://resources.download.minecraft.net", "https://resources.download.minecraft.net")
-        Return {
-            Original.
+        Return DlSourceOrder(
+            {Original},
+            {Original.
                 Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/assets").
                 Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/assets").
                 Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/assets").
                 Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/assets").
-                Replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets"),
-            Original
-        }
+                Replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets")
+            })
     End Function
     End Function
-
-    Public Function DlSourceLibraryGet(Original As String) As String()
+    ''' <summary>
+    ''' 下载 Libraries 文件。
+    ''' </summary>
+    Public Function DlSourceLibraryGet(Original As String) As IEnumerable(Of String)
         If {"minecraftforge", "fabricmc", "neoforged"}.Any(Function(k) Original.Contains(k)) Then '不添加原版源
         If {"minecraftforge", "fabricmc", "neoforged"}.Any(Function(k) Original.Contains(k)) Then '不添加原版源
             Return {
             Return {
                 Original.
                 Original.
@@ -1195,44 +1202,48 @@
                     Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
                     Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
             }
             }
         Else
         Else
-            Return {
-                Original.
+            Return DlSourceOrder(
+                {Original},
+                {Original.
                     Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven").
                     Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven").
                     Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven").
                     Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven").
                     Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven"),
                     Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven"),
-                Original.
+                 Original.
                     Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries").
                     Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries").
                     Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries").
                     Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries").
-                    Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries"),
-                Original
-            }
+                    Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
+                })
         End If
         End If
     End Function
     End Function
-
-    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
-
-    Public Function DlSourceLauncherOrMetaGet(Original As String) As String()
+    ''' <summary>
+    ''' 下载 Launcher 或 Meta 文件。
+    ''' 不应使用它来获取版本列表(因为它只使用文件下载源设置来决定源顺序)。
+    ''' </summary>
+    Public Function DlSourceLauncherOrMetaGet(Original As String) As IEnumerable(Of String)
         If Original Is Nothing Then Throw New Exception("无对应的 json 下载地址")
         If Original Is Nothing Then Throw New Exception("无对应的 json 下载地址")
-        Return {
-            Original.
+        Return DlSourceOrder(
+            {Original},
+            {Original.
                 Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com").
                 Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com").
                 Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com").
                 Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com").
                 Replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com").
                 Replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com").
-                Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com"),
-            Original
-        }
+                Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
+            })
+    End Function
+
+    'Mod 下载源
+    Public Function DlSourceModGet(Original As String) As String
+        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
     End Function
 
 
+    'Loader 自动切换
     Private Sub DlSourceLoader(Of InputType, OutputType)(MainLoader As LoaderTask(Of InputType, OutputType),
     Private Sub DlSourceLoader(Of InputType, OutputType)(MainLoader As LoaderTask(Of InputType, OutputType),
                                                          LoaderList As List(Of KeyValuePair(Of LoaderTask(Of InputType, OutputType), Integer)),
                                                          LoaderList As List(Of KeyValuePair(Of LoaderTask(Of InputType, OutputType), Integer)),
                                                          Optional IsForceRestart As Boolean = False)
                                                          Optional IsForceRestart As Boolean = False)
@@ -1251,7 +1262,7 @@
                     '检查加载器成功
                     '检查加载器成功
                     MainLoader.Output = SubLoader.Key.Output
                     MainLoader.Output = SubLoader.Key.Output
                     DlSourceLoaderAbort(LoaderList)
                     DlSourceLoaderAbort(LoaderList)
-                    Exit Sub
+                    Return
                 ElseIf BeforeLoadersAllFailed Then
                 ElseIf BeforeLoadersAllFailed Then
                     '此前的加载器全部失败,直接启动后续加载器
                     '此前的加载器全部失败,直接启动后续加载器
                     If WaitCycle < SubLoader.Value * 100 Then WaitCycle = SubLoader.Value * 100
                     If WaitCycle < SubLoader.Value * 100 Then WaitCycle = SubLoader.Value * 100
@@ -1293,7 +1304,7 @@
             '检查父加载器中断
             '检查父加载器中断
             If MainLoader.IsAborted Then
             If MainLoader.IsAborted Then
                 DlSourceLoaderAbort(LoaderList)
                 DlSourceLoaderAbort(LoaderList)
-                Exit Sub
+                Return
             End If
             End If
         Loop
         Loop
     End Sub
     End Sub

+ 34 - 41
Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb

@@ -10,7 +10,7 @@
 
 
         '路径
         '路径
         ''' <summary>
         ''' <summary>
-        ''' Java.exe 文件的完整路径。
+        ''' java.exe 文件的完整路径。
         ''' </summary>
         ''' </summary>
         Public ReadOnly Property PathJava As String
         Public ReadOnly Property PathJava As String
             Get
             Get
@@ -18,15 +18,7 @@
             End Get
             End Get
         End Property
         End Property
         ''' <summary>
         ''' <summary>
-        ''' Javaw.exe 文件的完整路径。
-        ''' </summary>
-        Public ReadOnly Property PathJavaw As String
-            Get
-                Return PathFolder & "javaw.exe"
-            End Get
-        End Property
-        ''' <summary>
-        ''' Javaw.exe 文件所在文件夹的路径,以 \ 结尾。
+        ''' java.exe 文件所在文件夹的路径,以 \ 结尾。
         ''' </summary>
         ''' </summary>
         Public PathFolder As String
         Public PathFolder As String
         ''' <summary>
         ''' <summary>
@@ -98,22 +90,19 @@
         ''' 检查并获取 Java 详细信息。在 Java 存在异常时抛出错误。
         ''' 检查并获取 Java 详细信息。在 Java 存在异常时抛出错误。
         ''' </summary>
         ''' </summary>
         Public Sub Check()
         Public Sub Check()
-            If IsChecked Then Exit Sub
+            If IsChecked Then Return
             Dim Output As String = Nothing
             Dim Output As String = Nothing
             Try
             Try
                 '确定文件存在
                 '确定文件存在
-                If Not File.Exists(PathJavaw) Then
-                    Throw New FileNotFoundException("未找到 javaw.exe 文件", PathJavaw)
-                End If
-                If Not File.Exists(PathFolder & "java.exe") Then
-                    Throw New FileNotFoundException("未找到 java.exe 文件", PathFolder & "java.exe")
-                End If
+                If Not File.Exists(PathJava) Then Throw New FileNotFoundException("未找到 java.exe 文件", PathJava)
+                If File.Exists(PathFolder & "pdf-bookmark") Then Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326
                 IsJre = Not File.Exists(PathFolder & "javac.exe")
                 IsJre = Not File.Exists(PathFolder & "javac.exe")
                 '运行 -version
                 '运行 -version
-                Output = ShellAndGetOutput(PathFolder & "java.exe", "-version", 15000).ToLower
+                Output = StartProcessAndGetOutput(PathJava, "-version", 15000).ToLower
                 If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败")
                 If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败")
-                If ModeDebug Then Log("[Java] Java 检查输出:" & PathFolder & "java.exe" & vbCrLf & Output)
+                If ModeDebug Then Log("[Java] Java 检查输出:" & PathJava & vbCrLf & Output)
                 If Output.Contains("/lib/ext exists") Then Throw New ApplicationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试")
                 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
                 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"
                 If VersionString.Split(".").Count > 4 Then VersionString = VersionString.Replace(".0.", ".") '#3493,VersionString = "21.0.2.0.2"
@@ -132,17 +121,17 @@
                 End If
                 End If
                 Is64Bit = Output.Contains("64-bit")
                 Is64Bit = Output.Contains("64-bit")
                 If Version.Minor <= 4 OrElse Version.Minor >= 100 Then Throw New ApplicationException("分析详细信息失败,获取的版本为 " & Version.ToString)
                 If Version.Minor <= 4 OrElse Version.Minor >= 100 Then Throw New ApplicationException("分析详细信息失败,获取的版本为 " & Version.ToString)
-                '基于 #3649,在 64 位系统上禁用 32 位 Java
+                '#3649:在 64 位系统上禁用 32 位 Java
                 If Not Is64Bit AndAlso Not Is32BitSystem Then Throw New Exception("该 Java 为 32 位版本,请安装 64 位的 Java")
                 If Not Is64Bit AndAlso Not Is32BitSystem Then Throw New Exception("该 Java 为 32 位版本,请安装 64 位的 Java")
-                '基于 #2249 发现 JRE 17 似乎也导致了 Forge 安装失败,干脆禁用更多版本的 JRE
+                '#2249:JRE 17 似乎会导致 Forge 安装失败,干脆禁用更多版本的 JRE
                 If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!")
                 If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!")
             Catch ex As ApplicationException
             Catch ex As ApplicationException
                 Throw ex
                 Throw ex
             Catch ex As ThreadInterruptedException
             Catch ex As ThreadInterruptedException
                 Throw ex
                 Throw ex
             Catch ex As Exception
             Catch ex As Exception
-                Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出"))
-                Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex)
+                Log("[Java] 检查失败的 Java 输出:" & If(PathJava, "Nothing") & vbCrLf & If(Output, "无程序输出"))
+                Throw New Exception("检查 Java 失败(" & If(PathJava, "Nothing") & ")", ex)
             End Try
             End Try
             IsChecked = True
             IsChecked = True
         End Sub
         End Sub
@@ -372,6 +361,7 @@ ExitUserJavaCheck:
             For Each Java In AllowedJavaList
             For Each Java In AllowedJavaList
                 '如果在官启文件夹启动,会将官启自带 Java 错误视作 MC 文件夹指定 Java,导致了 #2054 的第二例
                 '如果在官启文件夹启动,会将官启自带 Java 错误视作 MC 文件夹指定 Java,导致了 #2054 的第二例
                 If Java.PathFolder.Contains(".minecraft\cache\java") Then Continue For
                 If Java.PathFolder.Contains(".minecraft\cache\java") Then Continue For
+                If Java.PathFolder.Contains("PCL\MyDownload\") Then Continue For '#5780
                 If TargetJavaList.Contains(Java) Then
                 If TargetJavaList.Contains(Java) Then
                     '直接使用指定的 Java
                     '直接使用指定的 Java
                     AllowedJavaList = New List(Of JavaEntry) From {Java}
                     AllowedJavaList = New List(Of JavaEntry) From {Java}
@@ -455,10 +445,12 @@ NoUserJava:
     ''' 将 Java 按照适用性排序。
     ''' 将 Java 按照适用性排序。
     ''' </summary>
     ''' </summary>
     Public Function JavaSorter(Left As JavaEntry, Right As JavaEntry) As Boolean
     Public Function JavaSorter(Left As JavaEntry, Right As JavaEntry) As Boolean
+        Dim PathInfo As New DirectoryInfo(Path)
+        Dim PathMcInfo As New DirectoryInfo(PathMcFolder)
         '1. 尽量在当前文件夹或当前 Minecraft 文件夹
         '1. 尽量在当前文件夹或当前 Minecraft 文件夹
         Dim ProgramPathParent As String, MinecraftPathParent As String = ""
         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 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 Not Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Right.PathFolder.StartsWithF(ProgramPathParent) Then Return False
         If PathMcFolder <> "" Then
         If PathMcFolder <> "" Then
@@ -525,6 +517,8 @@ NoUserJava:
                 If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705)
                 If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705)
                 JavaSearchFolder(Disk.Name, JavaPreList, False)
                 JavaSearchFolder(Disk.Name, JavaPreList, False)
             Next
             Next
+            '查找 .jdks 文件夹中的 Java
+            JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\.jdks\", JavaPreList, False, True)
             '查找 APPDATA 文件夹中的 Java
             '查找 APPDATA 文件夹中的 Java
             JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\", JavaPreList, False)
             JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\", JavaPreList, False)
             JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) & "\", JavaPreList, False)
             JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) & "\", JavaPreList, False)
@@ -555,10 +549,9 @@ NoUserJava:
             '若不全为特殊引用,则清除特殊引用的地址
             '若不全为特殊引用,则清除特殊引用的地址
             Dim JavaWithoutInherit As New Dictionary(Of String, Boolean)
             Dim JavaWithoutInherit As New Dictionary(Of String, Boolean)
             For Each Pair In JavaPreList
             For Each Pair In JavaPreList
-                If Pair.Key.Contains("java8path_target_") OrElse Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") Then
-                    Log("[Java] 位于 " & Pair.Key & " 的 Java 包含特殊引用")
+                If Pair.Key.Contains("java8path_target_") OrElse Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") OrElse Pair.Key.ContainsF("system32") Then
+                    Log("[Java] 位于 " & Pair.Key & " 的 Java 位于特殊路径,不应优先使用")
                 Else
                 Else
-                    Log("[Java] 位于 " & Pair.Key & " 的 Java 不含特殊引用")
                     JavaWithoutInherit.Add(Pair.Key, Pair.Value)
                     JavaWithoutInherit.Add(Pair.Key, Pair.Value)
                 End If
                 End If
             Next
             Next
@@ -653,7 +646,7 @@ Wait:
     Private Sub JavaSearchFolder(OriginalPath As String, ByRef Results As Dictionary(Of String, Boolean), Source As Boolean, Optional IsFullSearch As Boolean = False)
     Private Sub JavaSearchFolder(OriginalPath As String, ByRef Results As Dictionary(Of String, Boolean), Source As Boolean, Optional IsFullSearch As Boolean = False)
         Try
         Try
             Log("[Java] 开始" & If(IsFullSearch, "完全", "部分") & "遍历查找:" & OriginalPath)
             Log("[Java] 开始" & If(IsFullSearch, "完全", "部分") & "遍历查找:" & OriginalPath)
-            JavaSearchFolder(New DirectoryInfo(ShortenPath(OriginalPath)), Results, Source, IsFullSearch)
+            JavaSearchFolder(New DirectoryInfo(OriginalPath), Results, Source, IsFullSearch)
         Catch ex As UnauthorizedAccessException
         Catch ex As UnauthorizedAccessException
             Log("[Java] 遍历查找 Java 时遭遇无权限的文件夹:" & OriginalPath)
             Log("[Java] 遍历查找 Java 时遭遇无权限的文件夹:" & OriginalPath)
         Catch ex As Exception
         Catch ex As Exception
@@ -668,20 +661,20 @@ Wait:
     Private Sub JavaSearchFolder(OriginalPath As DirectoryInfo, ByRef Results As Dictionary(Of String, Boolean), Source As Boolean, Optional IsFullSearch As Boolean = False)
     Private Sub JavaSearchFolder(OriginalPath As DirectoryInfo, ByRef Results As Dictionary(Of String, Boolean), Source As Boolean, Optional IsFullSearch As Boolean = False)
         Try
         Try
             '确认目录存在
             '确认目录存在
-            If Not OriginalPath.Exists Then Exit Sub
+            If Not OriginalPath.Exists Then Return
             Dim Path As String = OriginalPath.FullName.Replace("\\", "\")
             Dim Path As String = OriginalPath.FullName.Replace("\\", "\")
             If Not Path.EndsWithF("\") Then Path += "\"
             If Not Path.EndsWithF("\") Then Path += "\"
             '若该目录有 Java,则加入结果
             '若该目录有 Java,则加入结果
             If File.Exists(Path & "javaw.exe") Then Results(Path) = Source
             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",
                             "soft", "cache", "temp", "corretto", "roaming", "users", "craft", "program", "世界", "net",
                             "游戏", "oracle", "game", "file", "data", "jvm", "服务", "server", "客户", "client", "整合",
                             "游戏", "oracle", "game", "file", "data", "jvm", "服务", "server", "客户", "client", "整合",
                             "应用", "运行", "前置", "mojang", "官启", "新建文件夹", "eclipse", "microsoft", "hotspot",
                             "应用", "运行", "前置", "mojang", "官启", "新建文件夹", "eclipse", "microsoft", "hotspot",
                             "runtime", "x86", "x64", "forge", "原版", "optifine", "官方", "启动", "hmcl", "mod", "高清",
                             "runtime", "x86", "x64", "forge", "原版", "optifine", "官方", "启动", "hmcl", "mod", "高清",
                             "download", "launch", "程序", "path", "version", "baka", "pcl", "zulu", "local", "packages",
                             "download", "launch", "程序", "path", "version", "baka", "pcl", "zulu", "local", "packages",
-                            "4297127d64ec6", "1.", "启动"}
+                            "4297127d64ec6", "1.", "启动", "jbr"}
             For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories
             For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories
                 If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接
                 If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接
                 Dim SearchEntry = GetFolderNameFromPath(FolderInfo.Name).ToLower '用于搜索的字符串
                 Dim SearchEntry = GetFolderNameFromPath(FolderInfo.Name).ToLower '用于搜索的字符串
@@ -712,8 +705,8 @@ Wait:
             Return False
             Return False
         Else
         Else
             Return MyMsgBox($"PCL 未找到 {VersionDescription},是否需要 PCL 自动下载?" & vbCrLf &
             Return MyMsgBox($"PCL 未找到 {VersionDescription},是否需要 PCL 自动下载?" & vbCrLf &
-                            $"如果你已经安装了 {VersionDescription},请在 设置 → 启动选项 → 游戏 Java 中手动导入。",
-                            "未找到 Java", "自动下载", "取消") = 1
+                            $"如果你已经安装了 {VersionDescription},可以在 设置 → 启动选项 → 游戏 Java 中手动导入。",
+                            "自动下载 Java?", "自动下载", "取消") = 1
         End If
         End If
     End Function
     End Function
 
 
@@ -742,10 +735,10 @@ Wait:
     Private LastJavaBaseDir As String = Nothing '用于在下载中断或失败时删除未完成下载的 Java 文件夹,防止残留只下了一半但 -version 能跑的 Java
     Private LastJavaBaseDir As String = Nothing '用于在下载中断或失败时删除未完成下载的 Java 文件夹,防止残留只下了一半但 -version 能跑的 Java
     Private Sub JavaFileList(Loader As LoaderTask(Of Integer, List(Of NetFile)))
     Private Sub JavaFileList(Loader As LoaderTask(Of Integer, List(Of NetFile)))
         Log("[Java] 开始获取 Java 下载信息")
         Log("[Java] 开始获取 Java 下载信息")
-        Dim IndexFileStr As String = NetGetCodeByLoader(
-            {"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json",
-             "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"},
-        IsJson:=True)
+        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)
         '获取下载地址
         '获取下载地址
         Dim MainEntry As JObject = CType(GetJson(IndexFileStr), JObject)($"windows-x{If(Is32BitSystem, "86", "64")}")
         Dim MainEntry As JObject = CType(GetJson(IndexFileStr), JObject)($"windows-x{If(Is32BitSystem, "86", "64")}")
         Dim Entries = MainEntry.Children.Reverse. '选择最靠后的一项(最新)
         Dim Entries = MainEntry.Children.Reverse. '选择最靠后的一项(最新)
@@ -754,7 +747,7 @@ Wait:
         Dim Address As String = TargetEntry.Value("manifest")("url")
         Dim Address As String = TargetEntry.Value("manifest")("url")
         Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}")
         Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}")
         '获取文件列表
         '获取文件列表
-        Dim ListFileStr As String = NetGetCodeByLoader({Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com"), Address}, 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 & "\"
         LastJavaBaseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Key & "\"
         Dim Results As New List(Of NetFile)
         Dim Results As New List(Of NetFile)
         For Each File As JProperty In CType(GetJson(ListFileStr), JObject)("files")
         For Each File As JProperty In CType(GetJson(ListFileStr), JObject)("files")
@@ -767,7 +760,7 @@ Wait:
             End If
             End If
             If Checker.Check(LastJavaBaseDir & File.Name) Is Nothing Then Continue For '跳过已存在的文件
             If Checker.Check(LastJavaBaseDir & File.Name) Is Nothing Then Continue For '跳过已存在的文件
             Dim Url As String = Info("url")
             Dim Url As String = Info("url")
-            Results.Add(New NetFile({Url.Replace("piston-data.mojang.com", "bmclapi2.bangbang93.com"), Url}, LastJavaBaseDir & File.Name, Checker))
+            Results.Add(New NetFile(DlSourceOrder({Url}, {Url.Replace("piston-data.mojang.com", "bmclapi2.bangbang93.com")}), LastJavaBaseDir & File.Name, Checker))
         Next
         Next
         Loader.Output = Results
         Loader.Output = Results
         Log($"[Java] 需要下载 {Results.Count} 个文件,目标文件夹:{LastJavaBaseDir}")
         Log($"[Java] 需要下载 {Results.Count} 个文件,目标文件夹:{LastJavaBaseDir}")

+ 214 - 180
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -65,8 +65,8 @@ Public Module ModLaunch
     ''' 记录启动日志。
     ''' 记录启动日志。
     ''' </summary>
     ''' </summary>
     Public Sub McLaunchLog(Text As String)
     Public Sub McLaunchLog(Text As String)
-        Text = SecretFilter(Text, "*")
-        RunInUi(Sub() FrmLaunchRight.LabLog.Text += vbCrLf & "[" & GetTimeNow() & "] " & Text)
+        Text = FilterUserName(FilterAccessToken(Text, "*"), "*")
+        RunInUi(Sub() FrmLaunchRight.LabLog.Text += $"{vbCrLf}[{GetTimeNow()}] {Text}")
         Log("[Launch] " & Text)
         Log("[Launch] " & Text)
     End Sub
     End Sub
 
 
@@ -101,6 +101,8 @@ Public Module ModLaunch
             If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Critical)
             If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Critical)
             Throw
             Throw
         End Try
         End Try
+        Dim IsSavingBatch As Boolean = CurrentLaunchOptions?.SaveBatch IsNot Nothing
+
         '正式加载
         '正式加载
         Try
         Try
             '构造主加载器
             '构造主加载器
@@ -145,9 +147,15 @@ Public Module ModLaunch
             Select Case LaunchLoader.State
             Select Case LaunchLoader.State
                 Case LoadState.Finished
                 Case LoadState.Finished
                     Hint(McVersionCurrent.Name & " 启动成功!", HintType.Finish)
                     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
                 Case LoadState.Aborted
                     If AbortHint Is Nothing Then
                     If AbortHint Is Nothing Then
-                        Hint(If(CurrentLaunchOptions?.SaveBatch Is Nothing, "已取消启动!", "已取消导出启动脚本!"), HintType.Info)
+                        Hint(If(IsSavingBatch, "已取消导出启动脚本!", "已取消启动!"), HintType.Info)
                     Else
                     Else
                         Hint(AbortHint, HintType.Finish)
                         Hint(AbortHint, HintType.Finish)
                     End If
                     End If
@@ -162,7 +170,7 @@ NextInner:
             If CurrentEx.Message.StartsWithF("$") Then
             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
                 Throw
             ElseIf CurrentEx.InnerException IsNot Nothing Then
             ElseIf CurrentEx.InnerException IsNot Nothing Then
                 '检查下一级错误
                 '检查下一级错误
@@ -170,10 +178,17 @@ NextInner:
                 GoTo NextInner
                 GoTo NextInner
             Else
             Else
                 '没有特殊处理过的错误信息
                 '没有特殊处理过的错误信息
-                McLaunchLog("错误:" & GetExceptionDetail(ex))
+                McLaunchLog("错误:" & ex.GetDetail())
                 Log(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(ex.GetDetail(), "*"), "*"))
+                End If
                 Throw
                 Throw
             End If
             End If
         End Try
         End Try
@@ -578,11 +593,7 @@ SkipLogin:
     Private Sub McLoginServerStart(Data As LoaderTask(Of McLoginServer, McLoginResult))
     Private Sub McLoginServerStart(Data As LoaderTask(Of McLoginServer, McLoginResult))
         Dim Input As McLoginServer = Data.Input
         Dim Input As McLoginServer = Data.Input
         Dim NeedRefresh As Boolean = False, WasRefreshed As Boolean = False
         Dim NeedRefresh As Boolean = False, WasRefreshed As Boolean = False
-        Dim LogUsername As String = Input.UserName
-        If LogUsername.Contains("@") AndAlso Setup.Get("UiLauncherEmail") Then
-            LogUsername = AccountFilter(LogUsername)
-        End If
-        McLaunchLog("登录方式:" & Input.Description & "(" & LogUsername & ")")
+        McLaunchLog("登录方式:" & Input.Description)
         Data.Progress = 0.05
         Data.Progress = 0.05
         '尝试登录
         '尝试登录
         If (Not Data.Input.ForceReselectProfile) AndAlso
         If (Not Data.Input.ForceReselectProfile) AndAlso
@@ -598,12 +609,8 @@ SkipLogin:
                 McLoginRequestValidate(Data)
                 McLoginRequestValidate(Data)
                 GoTo LoginFinish
                 GoTo LoginFinish
             Catch ex As Exception
             Catch ex As Exception
-                Dim AllMessage = GetExceptionDetail(ex)
-                McLaunchLog("验证登录失败:" & AllMessage)
-                If (AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout")) AndAlso Not AllMessage.Contains("403") Then
-                    McLaunchLog("已触发超时登录失败")
-                    Throw New Exception("$登录失败:连接登录服务器超时。" & vbCrLf & "请检查你的网络状况是否良好,或尝试使用 VPN!")
-                End If
+                Log(ex, "验证登录失败")
+                If IsNetworkRelated(ex) Then Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后再试,或使用加速器、VPN 改善网络环境。")
             End Try
             End Try
             Data.Progress = 0.25
             Data.Progress = 0.25
             '尝试刷新登录
             '尝试刷新登录
@@ -613,7 +620,7 @@ Refresh:
                 McLoginRequestRefresh(Data, NeedRefresh)
                 McLoginRequestRefresh(Data, NeedRefresh)
                 GoTo LoginFinish
                 GoTo LoginFinish
             Catch ex As Exception
             Catch ex As Exception
-                McLaunchLog("刷新登录失败:" & GetExceptionDetail(ex))
+                McLaunchLog("刷新登录失败:" & ex.GetDetail())
                 If WasRefreshed Then Throw New Exception("二轮刷新登录失败", ex)
                 If WasRefreshed Then Throw New Exception("二轮刷新登录失败", ex)
             End Try
             End Try
             Data.Progress = If(NeedRefresh, 0.85, 0.45)
             Data.Progress = If(NeedRefresh, 0.85, 0.45)
@@ -623,7 +630,7 @@ Refresh:
             If Data.IsAborted Then Throw New ThreadInterruptedException
             If Data.IsAborted Then Throw New ThreadInterruptedException
             NeedRefresh = McLoginRequestLogin(Data)
             NeedRefresh = McLoginRequestLogin(Data)
         Catch ex As Exception
         Catch ex As Exception
-            McLaunchLog("登录失败:" & GetExceptionDetail(ex))
+            McLaunchLog("登录失败:" & ex.GetDetail())
             Throw
             Throw
         End Try
         End Try
         If NeedRefresh Then
         If NeedRefresh Then
@@ -688,11 +695,10 @@ LoginFinish:
         '发送登录请求
         '发送登录请求
         Dim RequestData As New JObject(
         Dim RequestData As New JObject(
             New JProperty("accessToken", AccessToken), New JProperty("clientToken", ClientToken), New JProperty("requestUser", True))
             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") '没有返回值的
             ContentType:="application/json; charset=utf-8") '没有返回值的
         '将登录结果输出
         '将登录结果输出
         Data.Output.AccessToken = AccessToken
         Data.Output.AccessToken = AccessToken
@@ -705,10 +711,9 @@ LoginFinish:
     End Sub
     End Sub
     Private Sub McLoginRequestRefresh(ByRef Data As LoaderTask(Of McLoginServer, McLoginResult), RequestUser As Boolean)
     Private Sub McLoginRequestRefresh(ByRef Data As LoaderTask(Of McLoginServer, McLoginResult), RequestUser As Boolean)
         McLaunchLog("刷新登录开始(Refresh, " & Data.Input.Token & ")")
         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("selectedProfile", New JObject(
                        New JProperty("name", Setup.Get($"Cache{Data.Input.Token}Name")),
                        New JProperty("name", Setup.Get($"Cache{Data.Input.Token}Name")),
                        New JProperty("id", Setup.Get($"Cache{Data.Input.Token}Uuid"))
                        New JProperty("id", Setup.Get($"Cache{Data.Input.Token}Uuid"))
@@ -716,8 +721,8 @@ LoginFinish:
                    New JProperty("accessToken", Setup.Get($"Cache{Data.Input.Token}Access")),
                    New JProperty("accessToken", Setup.Get($"Cache{Data.Input.Token}Access")),
                    New JProperty("requestUser", True)
                    New JProperty("requestUser", True)
                ).ToString(Newtonsoft.Json.Formatting.None),
                ).ToString(Newtonsoft.Json.Formatting.None),
-               Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
-               ContentType:="application/json; charset=utf-8"))
+               Headers:={{"Accept-Language", "zh-CN"}},
+               ContentType:="application/json; charset=utf-8", RequireJson:=True))
         '将登录结果输出
         '将登录结果输出
         If LoginJson("selectedProfile") Is Nothing Then Throw New Exception("选择的角色 " & Setup.Get("Cache" & Data.Input.Token & "Name") & " 无效!")
         If LoginJson("selectedProfile") Is Nothing Then Throw New Exception("选择的角色 " & Setup.Get("Cache" & Data.Input.Token & "Name") & " 无效!")
         Data.Output.AccessToken = LoginJson("accessToken").ToString
         Data.Output.AccessToken = LoginJson("accessToken").ToString
@@ -743,12 +748,11 @@ LoginFinish:
                 New JProperty("username", Data.Input.UserName),
                 New JProperty("username", Data.Input.UserName),
                 New JProperty("password", Data.Input.Password),
                 New JProperty("password", Data.Input.Password),
                 New JProperty("requestUser", True))
                 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"}},
-                ContentType:="application/json; charset=utf-8"))
+            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", RequireJson:=True))
             '检查登录结果
             '检查登录结果
             If LoginJson("availableProfiles").Count = 0 Then
             If LoginJson("availableProfiles").Count = 0 Then
                 If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Critical)
                 If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Critical)
@@ -806,13 +810,14 @@ LoginFinish:
             McLaunchLog("登录成功(Login, " & Data.Input.Token & ")")
             McLaunchLog("登录成功(Login, " & Data.Input.Token & ")")
             Return NeedRefresh
             Return NeedRefresh
         Catch ex As Exception
         Catch ex As Exception
-            Dim AllMessage As String = GetExceptionSummary(ex)
+            Dim AllMessage As String = ex.GetBrief()
             Log(ex, "登录失败原始错误信息", LogLevel.Normal)
             Log(ex, "登录失败原始错误信息", LogLevel.Normal)
             '读取服务器返回的错误
             '读取服务器返回的错误
             If TypeOf ex Is ResponsedWebException Then
             If TypeOf ex Is ResponsedWebException Then
                 Dim ErrorMessage As String = Nothing
                 Dim ErrorMessage As String = Nothing
                 Try
                 Try
-                    ErrorMessage = GetJson(DirectCast(ex, ResponsedWebException).Response)("errorMessage")
+                    Dim Response = DirectCast(ex, ResponsedWebException).Response
+                    If ErrorMessage.ContainsF("errorMessage", True) Then ErrorMessage = GetJson(Response)("errorMessage")
                 Catch
                 Catch
                 End Try
                 End Try
                 If Not String.IsNullOrWhiteSpace(ErrorMessage) Then
                 If Not String.IsNullOrWhiteSpace(ErrorMessage) Then
@@ -830,7 +835,7 @@ LoginFinish:
                 End If
                 End If
             End If
             End If
             '通用关键字检测
             '通用关键字检测
-            If AllMessage.Contains("403") Then
+            If AllMessage.Contains("(403)") Then
                 Select Case Data.Input.Type
                 Select Case Data.Input.Type
                     Case McLoginType.Auth
                     Case McLoginType.Auth
                         Throw New Exception("$登录失败,以下为可能的原因:" & vbCrLf &
                         Throw New Exception("$登录失败,以下为可能的原因:" & vbCrLf &
@@ -844,8 +849,8 @@ LoginFinish:
                                             If(Data.Input.UserName.Contains("@"), "", " - 登录账号应为邮箱或统一通行证账号,而非游戏角色 ID。" & vbCrLf) &
                                             If(Data.Input.UserName.Contains("@"), "", " - 登录账号应为邮箱或统一通行证账号,而非游戏角色 ID。" & vbCrLf) &
                                             " - 只注册了账号,但没有加入对应服务器。")
                                             " - 只注册了账号,但没有加入对应服务器。")
                 End Select
                 End Select
-            ElseIf AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout") OrElse AllMessage.Contains("网络请求失败") Then
-                Throw New Exception("$登录失败:连接登录服务器超时。" & vbCrLf & "请检查你的网络状况是否良好,或尝试使用 VPN!")
+            ElseIf ex.IsNetworkRelated() Then
+                Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后再试,或使用加速器、VPN 改善网络环境。")
             ElseIf ex.Message.StartsWithF("$") Then
             ElseIf ex.Message.StartsWithF("$") Then
                 Throw
                 Throw
             Else
             Else
@@ -862,8 +867,9 @@ LoginFinish:
         '初始请求
         '初始请求
 Retry:
 Retry:
         McLaunchLog("开始微软登录步骤 1/6(原始登录)")
         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", RequireJson:=True))
         McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString)
         McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString)
 
 
         '弹窗
         '弹窗
@@ -892,13 +898,23 @@ Retry:
 
 
         Dim Result As String
         Dim Result As String
         Try
         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
+            '修改错误列表时,同时检查 MyMsgLogin.xaml.vb 中的对应代码
+            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", ""}
                 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
             Else
                 Throw
                 Throw
             End If
             End If
@@ -922,8 +938,8 @@ Retry:
            ""RelyingParty"": ""http://auth.xboxlive.com"",
            ""RelyingParty"": ""http://auth.xboxlive.com"",
            ""TokenType"": ""JWT""
            ""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", RequireJson:=True)
         Dim ResultJson As JObject = GetJson(Result)
         Dim ResultJson As JObject = GetJson(Result)
         Dim XBLToken As String = ResultJson("Token").ToString
         Dim XBLToken As String = ResultJson("Token").ToString
         Return XBLToken
         Return XBLToken
@@ -944,21 +960,22 @@ Retry:
                                  }"
                                  }"
         Dim Result As String
         Dim Result As String
         Try
         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", RequireJson:=True)
+        Catch ex As ResponsedWebException
             '参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js
             '参考 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)
                 MyMsgBox("该账号似乎已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True)
                 Throw New Exception("$$")
                 Throw New Exception("$$")
-            ElseIf ex.Message.Contains("2148916233") Then
+            ElseIf ex.Response.Contains("2148916233") Then
                 If MyMsgBox("你尚未注册 Xbox 账户,请在注册后再登录。", "登录提示", "注册", "取消") = 1 Then
                 If MyMsgBox("你尚未注册 Xbox 账户,请在注册后再登录。", "登录提示", "注册", "取消") = 1 Then
                     OpenWebsite("https://signup.live.com/signup")
                     OpenWebsite("https://signup.live.com/signup")
                 End If
                 End If
                 Throw New Exception("$$")
                 Throw New Exception("$$")
-            ElseIf ex.Message.Contains("2148916235") Then
-                MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请尝试使用加速器或 VPN。", "登录失败", "我知道了")
+            ElseIf ex.Response.Contains("2148916235") Then
+                MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请使用加速器或 VPN,然后再试。", "登录失败", "我知道了")
                 Throw New Exception("$$")
                 Throw New Exception("$$")
-            ElseIf ex.Message.Contains("2148916238") Then
+            ElseIf ex.Response.Contains("2148916238") Then
                 If MyMsgBox("该账号年龄不足,你需要先修改出生日期,然后才能登录。" & vbCrLf &
                 If MyMsgBox("该账号年龄不足,你需要先修改出生日期,然后才能登录。" & vbCrLf &
                             "该账号目前填写的年龄是否在 13 岁以上?", "登录提示", "13 岁以上", "12 岁以下", "我不知道") = 1 Then
                             "该账号目前填写的年龄是否在 13 岁以上?", "登录提示", "13 岁以上", "12 岁以下", "我不知道") = 1 Then
                     OpenWebsite("https://account.live.com/editprof.aspx")
                     OpenWebsite("https://account.live.com/editprof.aspx")
@@ -976,9 +993,10 @@ Retry:
         End Try
         End Try
 
 
         Dim ResultJson As JObject = GetJson(Result)
         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}
+        Return {
+            ResultJson("Token"), 'XSTSToken
+            ResultJson("DisplayClaims")("xui")(0)("uhs") 'UHS
+        }
     End Function
     End Function
     '微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken
     '微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken
     Private Function MsLoginStep4(Tokens As String()) As String
     Private Function MsLoginStep4(Tokens As String()) As String
@@ -987,29 +1005,29 @@ Retry:
         Dim Request As String = New JObject(New JProperty("identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}")).ToString(0)
         Dim Request As String = New JObject(New JProperty("identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}")).ToString(0)
         Dim Result As String
         Dim Result As String
         Try
         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", RequireJson:=True)
         Catch ex As Net.WebException
         Catch ex As Net.WebException
-            Dim Message As String = GetExceptionSummary(ex)
+            Dim Message As String = ex.GetBrief()
             If Message.Contains("(429)") Then
             If Message.Contains("(429)") Then
-                Log(ex, "微软登录第 5 步汇报 429")
+                Log(ex, "微软登录第 4 步汇报 429")
                 Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
                 Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
             ElseIf Message.Contains("(403)") Then
             ElseIf Message.Contains("(403)") Then
-                Log(ex, "微软登录第 5 步汇报 403")
+                Log(ex, "微软登录第 4 步汇报 403")
                 Throw New Exception("$当前 IP 的登录尝试异常。" & vbCrLf & "如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!")
                 Throw New Exception("$当前 IP 的登录尝试异常。" & vbCrLf & "如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!")
             Else
             Else
                 Throw
                 Throw
             End If
             End If
         End Try
         End Try
 
 
-        Dim ResultJson As JObject = GetJson(Result)
-        Dim AccessToken As String = ResultJson("access_token").ToString
-        Return AccessToken
+        Return GetJson(Result)("access_token").ToString
     End Function
     End Function
     '微软登录步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP
     '微软登录步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP
     Private Sub MsLoginStep5(AccessToken As String)
     Private Sub MsLoginStep5(AccessToken As String)
         McLaunchLog("开始微软登录步骤 5/6")
         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}}, RequireJson:=True)
         Try
         Try
             Dim ResultJson As JObject = GetJson(Result)
             Dim ResultJson As JObject = GetJson(Result)
             If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then
             If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then
@@ -1020,7 +1038,7 @@ Retry:
                 Throw New Exception("$$")
                 Throw New Exception("$$")
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "微软登录第 6 步异常:" & Result)
+            Log(ex, "微软登录第 5 步异常:" & Result)
             Throw
             Throw
         End Try
         End Try
     End Sub
     End Sub
@@ -1030,14 +1048,15 @@ Retry:
 
 
         Dim Result As String
         Dim Result As String
         Try
         Try
-            Result = NetRequestMultiple("https://api.minecraftservices.com/minecraft/profile", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
-        Catch ex As Net.WebException
-            Dim Message As String = GetExceptionSummary(ex)
+            Result = NetRequestByClientMultiple("https://api.minecraftservices.com/minecraft/profile",
+                ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}, RequireJson:=True)
+        Catch ex As WebException
+            Dim Message As String = ex.GetBrief()
             If Message.Contains("(429)") Then
             If Message.Contains("(429)") Then
-                Log(ex, "微软登录第 7 步汇报 429")
+                Log(ex, "微软登录第 6 步汇报 429")
                 Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
                 Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
             ElseIf Message.Contains("(404)") Then
             ElseIf Message.Contains("(404)") Then
-                Log(ex, "微软登录第 7 步汇报 404")
+                Log(ex, "微软登录第 6 步汇报 404")
                 RunInNewThread(
                 RunInNewThread(
                 Sub()
                 Sub()
                     Select Case MyMsgBox("请先创建 Minecraft 玩家档案,然后再重新登录。", "登录失败", "创建档案", "取消")
                     Select Case MyMsgBox("请先创建 Minecraft 玩家档案,然后再重新登录。", "登录失败", "创建档案", "取消")
@@ -1084,8 +1103,7 @@ Retry:
                         Uuid = McLoginMojangUuid(SkinName, False)
                         Uuid = McLoginMojangUuid(SkinName, False)
                     End If
                     End If
                 Catch ex As Exception
                 Catch ex As Exception
-                    Log(ex, "离线启动时使用的正版皮肤获取失败")
-                    MyMsgBox("由于设置的离线启动时使用的正版皮肤获取失败,游戏将以无皮肤的方式启动。" & vbCrLf & "请检查你的网络是否通畅,或尝试使用 VPN!" & vbCrLf & vbCrLf & "详细的错误信息:" & ex.Message, "皮肤获取失败")
+                    Log(ex, "皮肤信息获取失败,游戏将以无皮肤的方式启动", LogLevel.Hint)
                 End Try
                 End Try
             Case 4
             Case 4
                 '自定义
                 '自定义
@@ -1104,7 +1122,7 @@ Retry:
         If Len(Uuid) = 32 Then Return Uuid
         If Len(Uuid) = 32 Then Return Uuid
         '从官网获取
         '从官网获取
         Try
         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, RequireJson:=True))
             If GotJson Is Nothing Then Throw New FileNotFoundException("正版玩家档案不存在(" & Name & ")")
             If GotJson Is Nothing Then Throw New FileNotFoundException("正版玩家档案不存在(" & Name & ")")
             Uuid = If(GotJson("id"), "")
             Uuid = If(GotJson("id"), "")
         Catch ex As Exception
         Catch ex As Exception
@@ -1134,23 +1152,22 @@ Retry:
         Dim MinVer As New Version(0, 0, 0, 0), MaxVer As New Version(999, 999, 999, 999)
         Dim MinVer As New Version(0, 0, 0, 0), MaxVer As New Version(999, 999, 999, 999)
 
 
         'MC 大版本检测
         'MC 大版本检测
-        If (McVersionCurrent.ReleaseTime >= New Date(2024, 4, 2) AndAlso McVersionCurrent.Version.McCodeMain = 99) OrElse
-            (McVersionCurrent.Version.McCodeMain > 20 AndAlso McVersionCurrent.Version.McCodeMain <> 99) OrElse
-            (McVersionCurrent.Version.McCodeMain = 20 AndAlso McVersionCurrent.Version.McCodeSub >= 5) Then
+        If (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2024, 4, 2)) OrElse
+           (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 20, 5)) Then
             '1.20.5+(24w14a+):至少 Java 21
             '1.20.5+(24w14a+):至少 Java 21
             MinVer = New Version(1, 21, 0, 0)
             MinVer = New Version(1, 21, 0, 0)
-        ElseIf (McVersionCurrent.ReleaseTime >= New Date(2021, 11, 16) AndAlso McVersionCurrent.Version.McCodeMain = 99) OrElse
-            (McVersionCurrent.Version.McCodeMain >= 18 AndAlso McVersionCurrent.Version.McCodeMain <> 99) Then
+        ElseIf (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2021, 11, 16)) OrElse
+            (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 18)) Then
             '1.18 pre2+:至少 Java 17
             '1.18 pre2+:至少 Java 17
             MinVer = New Version(1, 17, 0, 0)
             MinVer = New Version(1, 17, 0, 0)
-        ElseIf (McVersionCurrent.ReleaseTime >= New Date(2021, 5, 11) AndAlso McVersionCurrent.Version.McCodeMain = 99) OrElse
-           (McVersionCurrent.Version.McCodeMain >= 17 AndAlso McVersionCurrent.Version.McCodeMain <> 99) Then
+        ElseIf (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2021, 5, 11)) OrElse
+           (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 17)) Then
             '1.17+ (21w19a+):至少 Java 16
             '1.17+ (21w19a+):至少 Java 16
             MinVer = New Version(1, 16, 0, 0)
             MinVer = New Version(1, 16, 0, 0)
         ElseIf McVersionCurrent.ReleaseTime.Year >= 2017 Then 'Minecraft 1.12 与 1.11 的分界线正好是 2017 年,太棒了
         ElseIf McVersionCurrent.ReleaseTime.Year >= 2017 Then 'Minecraft 1.12 与 1.11 的分界线正好是 2017 年,太棒了
             '1.12+:至少 Java 8
             '1.12+:至少 Java 8
             MinVer = New Version(1, 8, 0, 0)
             MinVer = New Version(1, 8, 0, 0)
-        ElseIf McVersionCurrent.ReleaseTime <= New Date(2013, 5, 1) AndAlso McVersionCurrent.ReleaseTime.Year >= 2001 Then '避免某些版本的 1960 癌
+        ElseIf McVersionCurrent.ReleaseTime <= New Date(2013, 5, 1) AndAlso McVersionCurrent.ReleaseTime.Year >= 2001 Then '避免某些版本写个 1960 年
             '1.5.2-:最高 Java 12
             '1.5.2-:最高 Java 12
             MaxVer = New Version(1, 12, 999, 999)
             MaxVer = New Version(1, 12, 999, 999)
         End If
         End If
@@ -1161,11 +1178,11 @@ Retry:
         End If
         End If
 
 
         'OptiFine 检测
         'OptiFine 检测
-        If McVersionCurrent.Version.HasOptiFine Then
-            If McVersionCurrent.Version.McCodeMain <= 7 AndAlso McVersionCurrent.Version.McCodeMain > 0 Then
+        If McVersionCurrent.Version.HasOptiFine AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本
+            If McVersionCurrent.Version.McVersion < New Version(1, 7) Then
                 '<1.7:至多 Java 8
                 '<1.7:至多 Java 8
                 MaxVer = New Version(1, 8, 999, 999)
                 MaxVer = New Version(1, 8, 999, 999)
-            ElseIf McVersionCurrent.Version.McCodeMain >= 8 AndAlso McVersionCurrent.Version.McCodeMain <= 11 Then
+            ElseIf McVersionCurrent.Version.McVersion >= New Version(1, 8) AndAlso McVersionCurrent.Version.McVersion < New Version(1, 12) Then
                 '1.8 - 1.11:必须恰好 Java 8
                 '1.8 - 1.11:必须恰好 Java 8
                 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 8, 999, 999)
                 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 8, 999, 999)
             ElseIf McVersionCurrent.Version.McCodeMain = 12 Then
             ElseIf McVersionCurrent.Version.McCodeMain = 12 Then
@@ -1176,14 +1193,14 @@ Retry:
 
 
         'Forge 检测
         'Forge 检测
         If McVersionCurrent.Version.HasForge Then
         If McVersionCurrent.Version.HasForge Then
-            If McVersionCurrent.Version.McName = "1.7.2" Then
-                '1.7.2:必须 Java 7
+            If McVersionCurrent.Version.McVersion >= New Version(1, 6, 1) AndAlso McVersionCurrent.Version.McVersion <= New Version(1, 7, 2) Then
+                '1.6.1 - 1.7.2:必须 Java 7
                 MinVer = If(New Version(1, 7, 0, 0) > MinVer, New Version(1, 7, 0, 0), MinVer)
                 MinVer = If(New Version(1, 7, 0, 0) > MinVer, New Version(1, 7, 0, 0), MinVer)
                 MaxVer = If(New Version(1, 7, 999, 999) < MaxVer, New Version(1, 7, 999, 999), MaxVer)
                 MaxVer = If(New Version(1, 7, 999, 999) < MaxVer, New Version(1, 7, 999, 999), MaxVer)
-            ElseIf McVersionCurrent.Version.McCodeMain <= 12 AndAlso McVersionCurrent.Version.McCodeMain > 0 Then
+            ElseIf McVersionCurrent.Version.McCodeMain <= 12 OrElse Not McVersionCurrent.Version.IsStandardVersion Then '非标准版本
                 '<=1.12:Java 8
                 '<=1.12:Java 8
                 MaxVer = New Version(1, 8, 999, 999)
                 MaxVer = New Version(1, 8, 999, 999)
-            ElseIf McVersionCurrent.Version.McCodeMain <= 14 AndAlso McVersionCurrent.Version.McCodeMain >= 13 Then
+            ElseIf McVersionCurrent.Version.McCodeMain <= 14 Then
                 '1.13 - 1.14:Java 8 - 10
                 '1.13 - 1.14:Java 8 - 10
                 MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer)
                 MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer)
                 MaxVer = If(New Version(1, 10, 999, 999) < MaxVer, New Version(1, 10, 999, 999), MaxVer)
                 MaxVer = If(New Version(1, 10, 999, 999) < MaxVer, New Version(1, 10, 999, 999), MaxVer)
@@ -1201,16 +1218,22 @@ Retry:
         End If
         End If
 
 
         'Fabric 检测
         'Fabric 检测
-        If McVersionCurrent.Version.HasFabric Then
-            If McVersionCurrent.Version.McCodeMain >= 15 AndAlso McVersionCurrent.Version.McCodeMain <= 16 AndAlso McVersionCurrent.Version.McCodeMain <> -1 Then
+        If McVersionCurrent.Version.HasFabric AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本
+            If McVersionCurrent.Version.McCodeMain >= 15 AndAlso McVersionCurrent.Version.McCodeMain <= 16 Then
                 '1.15 - 1.16:Java 8+
                 '1.15 - 1.16:Java 8+
                 MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer)
                 MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer)
-            ElseIf McVersionCurrent.Version.McCodeMain >= 18 AndAlso McVersionCurrent.Version.McCodeMain < 99 Then
+            ElseIf McVersionCurrent.Version.McCodeMain >= 18 Then
                 '1.18+:Java 17+
                 '1.18+:Java 17+
                 MinVer = If(New Version(1, 17, 0, 0) > MinVer, New Version(1, 17, 0, 0), MinVer)
                 MinVer = If(New Version(1, 17, 0, 0) > MinVer, New Version(1, 17, 0, 0), MinVer)
             End If
             End If
         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
         If Setup.Get("LoginType") = McLoginType.Nide Then
             '至少 Java 8u101
             '至少 Java 8u101
@@ -1222,28 +1245,32 @@ Retry:
             '选择 Java
             '选择 Java
             McLaunchLog("Java 版本需求:最低 " & MinVer.ToString & ",最高 " & MaxVer.ToString)
             McLaunchLog("Java 版本需求:最低 " & MinVer.ToString & ",最高 " & MaxVer.ToString)
             McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent)
             McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent)
-            If Task.IsAborted Then Exit Sub
+            If Task.IsAborted Then Return
             If McLaunchJavaSelected IsNot Nothing Then
             If McLaunchJavaSelected IsNot Nothing Then
                 McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString)
                 McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString)
-                Exit Sub
+                Return
             End If
             End If
 
 
             '无合适的 Java
             '无合适的 Java
-            If Task.IsAborted Then Exit Sub '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java
+            If Task.IsAborted Then Return '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java
             McLaunchLog("无合适的 Java,需要确认是否自动下载")
             McLaunchLog("无合适的 Java,需要确认是否自动下载")
             Dim JavaCode As String
             Dim JavaCode As String
-            If MinVer >= New Version(1, 22, 0, 0) Then '潜在的向后兼容
+            If MinVer >= New Version(1, 22) Then '潜在的向后兼容
                 JavaCode = MinVer.Minor
                 JavaCode = MinVer.Minor
                 If Not JavaDownloadConfirm("Java " & JavaCode) Then Throw New Exception("$$")
                 If Not JavaDownloadConfirm("Java " & JavaCode) Then Throw New Exception("$$")
-            ElseIf MinVer >= New Version(1, 21, 0, 0) Then
+            ElseIf MinVer >= New Version(1, 21) Then
                 JavaCode = 21
                 JavaCode = 21
                 If Not JavaDownloadConfirm("Java 21") Then Throw New Exception("$$")
                 If Not JavaDownloadConfirm("Java 21") Then Throw New Exception("$$")
-            ElseIf MinVer >= New Version(1, 9, 0, 0) Then
+            ElseIf MinVer >= New Version(1, 9) Then
                 JavaCode = 17
                 JavaCode = 17
                 If Not JavaDownloadConfirm("Java 17") Then Throw New Exception("$$")
                 If Not JavaDownloadConfirm("Java 17") Then Throw New Exception("$$")
-            ElseIf MaxVer < New Version(1, 8, 0, 0) Then
+            ElseIf MaxVer < New Version(1, 8) Then
                 JavaCode = 7
                 JavaCode = 7
-                If Not JavaDownloadConfirm("Java 7", True) Then Throw New Exception("$$")
+                If McVersionCurrent.Version.HasForge Then
+                    MyMsgBox("你需要先安装 LegacyJavaFixer Mod,或自行安装 Java 7,然后才能启动该版本。", "未找到 Java")
+                Else
+                    If Not JavaDownloadConfirm("Java 7", True) Then Throw New Exception("$$")
+                End If
             ElseIf MinVer > New Version(1, 8, 0, 140) AndAlso MaxVer < New Version(1, 8, 0, 321) Then
             ElseIf MinVer > New Version(1, 8, 0, 140) AndAlso MaxVer < New Version(1, 8, 0, 321) Then
                 JavaCode = "8u141"
                 JavaCode = "8u141"
                 If Not JavaDownloadConfirm("Java 8.0.141 ~ 8.0.320", True) Then Throw New Exception("$$")
                 If Not JavaDownloadConfirm("Java 8.0.141 ~ 8.0.320", True) Then Throw New Exception("$$")
@@ -1273,7 +1300,7 @@ Retry:
             '检查下载结果
             '检查下载结果
             If JavaSearchLoader.State <> LoadState.Loading Then JavaSearchLoader.State = LoadState.Waiting '2872#
             If JavaSearchLoader.State <> LoadState.Loading Then JavaSearchLoader.State = LoadState.Waiting '2872#
             McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent)
             McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent)
-            If Task.IsAborted Then Exit Sub
+            If Task.IsAborted Then Return
             If McLaunchJavaSelected IsNot Nothing Then
             If McLaunchJavaSelected IsNot Nothing Then
                 McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString)
                 McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString)
             Else
             Else
@@ -1350,22 +1377,14 @@ Retry:
             Arguments += " " & McLaunchArgumentsGameNew(McVersionCurrent)
             Arguments += " " & McLaunchArgumentsGameNew(McVersionCurrent)
             McLaunchLog("新版 Game 参数获取成功")
             McLaunchLog("新版 Game 参数获取成功")
         End If
         End If
-        '编码参数(#5818、#5892)
-        If McLaunchJavaSelected.VersionCode > 8 Then
-            If Not Arguments.Contains("-Dfile.encoding=") Then Arguments += " -Dfile.encoding=UTF-8"
-            If Not Arguments.Contains("-Dstdout.encoding=") Then Arguments += " -Dstdout.encoding=UTF-8"
-            If Not Arguments.Contains("-Dstderr.encoding=") Then Arguments += " -Dstderr.encoding=UTF-8"
+        '编码参数(#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
         End If
-        '替换参数
-        Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader)
-        If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
-            '若自定义信息为空,则去掉该部分
-            Arguments = Arguments.Replace(" --versionType ${version_type}", "")
-            ReplaceArguments("${version_type}") = """"""
+        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
         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
         'MJSB
         Arguments = Arguments.Replace(" -Dos.name=Windows 10", " -Dos.name=""Windows 10""")
         Arguments = Arguments.Replace(" -Dos.name=Windows 10", " -Dos.name=""Windows 10""")
         '全屏
         '全屏
@@ -1374,31 +1393,47 @@ Retry:
         For Each Arg In CurrentLaunchOptions.ExtraArgs
         For Each Arg In CurrentLaunchOptions.ExtraArgs
             Arguments += " " & Arg.Trim
             Arguments += " " & Arg.Trim
         Next
         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)
         Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp)
         If Server.Length > 0 Then
         If Server.Length > 0 Then
             If McVersionCurrent.ReleaseTime > New Date(2023, 4, 4) Then
             If McVersionCurrent.ReleaseTime > New Date(2023, 4, 4) Then
                 'QuickPlay
                 'QuickPlay
-                Arguments += $" --quickPlayMultiplayer ""{Server}"""
+                FinalArguments += $" --quickPlayMultiplayer ""{Server}"""
             Else
             Else
                 '老版本
                 '老版本
                 If Server.Contains(":") Then
                 If Server.Contains(":") Then
                     '包含端口号
                     '包含端口号
-                    Arguments += " --server " & Server.Split(":")(0) & " --port " & Server.Split(":")(1)
+                    FinalArguments += $" --server {Server.Split(":")(0)} --port {Server.Split(":")(1)}"
                 Else
                 Else
                     '不包含端口号
                     '不包含端口号
-                    Arguments += " --server " & Server & " --port 25565"
+                    FinalArguments += $" --server {Server} --port 25565"
                 End If
                 End If
                 If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Critical)
                 If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Critical)
             End If
             End If
         End If
         End If
-        '自定义
-        Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent)
-        Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame)
         '输出
         '输出
         McLaunchLog("Minecraft 启动参数:")
         McLaunchLog("Minecraft 启动参数:")
-        McLaunchLog(Arguments)
-        McLaunchArgument = Arguments
+        McLaunchLog(FinalArguments)
+        McLaunchArgument = FinalArguments
     End Sub
     End Sub
 
 
     'Jvm 部分(第一段)
     'Jvm 部分(第一段)
@@ -1424,11 +1459,12 @@ Retry:
         End If
         End If
         'Authlib-Injector
         'Authlib-Injector
         If McLoginLoader.Output.Type = "Auth" Then
         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,
             Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy,
                 "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录
                 "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录
                 Setup.Get("VersionServerAuthServer", McVersionCurrent))
                 Setup.Get("VersionServerAuthServer", McVersionCurrent))
             Try
             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 &
                 DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
@@ -1451,6 +1487,9 @@ Retry:
             DataList.Add(Version.JsonObject("mainClass"))
             DataList.Add(Version.JsonObject("mainClass"))
         End If
         End If
 
 
+        '处理 DataList 的换行符
+        DataList = DataList.Select(Function(d) d.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ").Trim).ToList
+
         Return Join(DataList, " ")
         Return Join(DataList, " ")
     End Function
     End Function
     Private Function McLaunchArgumentsJvmNew(Version As McVersion) As String
     Private Function McLaunchArgumentsJvmNew(Version As McVersion) As String
@@ -1493,11 +1532,10 @@ NextVersion:
         End If
         End If
         'Authlib-Injector
         'Authlib-Injector
         If McLoginLoader.Output.Type = "Auth" Then
         If McLoginLoader.Output.Type = "Auth" Then
-            Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy,
-                "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录
-                Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
+            If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#)
+            Dim Server As String = Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent)
             Try
             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 &
                 DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.side=client" &
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
                               " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
@@ -1517,7 +1555,7 @@ NextVersion:
         '如果不合并,会导致 Forge 1.17 启动无效,它有两个 --add-exports,进一步导致其中一个在后面被去重
         '如果不合并,会导致 Forge 1.17 启动无效,它有两个 --add-exports,进一步导致其中一个在后面被去重
         Dim DeDuplicateDataList As New List(Of String)
         Dim DeDuplicateDataList As New List(Of String)
         For i = 0 To DataList.Count - 1
         For i = 0 To DataList.Count - 1
-            Dim CurrentEntry As String = DataList(i)
+            Dim CurrentEntry As String = DataList(i).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ").Trim
             If DataList(i).StartsWithF("-") Then
             If DataList(i).StartsWithF("-") Then
                 Do While i < DataList.Count - 1
                 Do While i < DataList.Count - 1
                     If DataList(i + 1).StartsWithF("-") Then
                     If DataList(i + 1).StartsWithF("-") Then
@@ -1531,10 +1569,8 @@ NextVersion:
             DeDuplicateDataList.Add(CurrentEntry.Trim.Replace("McEmu= ", "McEmu="))
             DeDuplicateDataList.Add(CurrentEntry.Trim.Replace("McEmu= ", "McEmu="))
         Next
         Next
 
 
-        '#3511 的清理
-        DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M")
-
         '去重
         '去重
+        DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") '#3511 的清理
         Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ")
         Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ")
 
 
         '添加 MainClass
         '添加 MainClass
@@ -1671,20 +1707,21 @@ NextVersion:
         GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken)
         GameArguments.Add("${user_type}", "msa") '#1221
         GameArguments.Add("${user_type}", "msa") '#1221
+        GameArguments.Add("${primary_jar}", Version.Path & Version.Name & ".jar") '#6942
 
 
         '窗口尺寸参数
         '窗口尺寸参数
         Dim GameSize As Size
         Dim GameSize As Size
         Select Case Setup.Get("LaunchArgumentWindowType")
         Select Case Setup.Get("LaunchArgumentWindowType")
-            Case 2
+            Case 2 '与启动器尺寸一致
                 Dim Result As Size
                 Dim Result As Size
                 RunInUiWait(Sub() Result = New Size(GetPixelSize(FrmMain.PanForm.ActualWidth), GetPixelSize(FrmMain.PanForm.ActualHeight)))
                 RunInUiWait(Sub() Result = New Size(GetPixelSize(FrmMain.PanForm.ActualWidth), GetPixelSize(FrmMain.PanForm.ActualHeight)))
                 GameSize = Result
                 GameSize = Result
-            Case 3
-                GameSize = New Size(Math.Max(100, Setup.Get("LaunchArgumentWindowWidth") - 2), Math.Max(100, Setup.Get("LaunchArgumentWindowHeight") - 2))
+                GameSize.Height -= 29.5 * DPI / 96 '标题栏高度
+            Case 3 '自定义
+                GameSize = New Size(Math.Max(100, Setup.Get("LaunchArgumentWindowWidth")), Math.Max(100, Setup.Get("LaunchArgumentWindowHeight")))
             Case Else
             Case Else
-                GameSize = New Size(875 - 2, 540 - 2)
+                GameSize = New Size(854, 480)
         End Select
         End Select
-        GameSize.Height -= 29.5 * DPI / 96 '标题栏高度
         If McVersionCurrent.Version.McCodeMain <= 12 AndAlso
         If McVersionCurrent.Version.McCodeMain <= 12 AndAlso
             McLaunchJavaSelected.VersionCode <= 8 AndAlso McLaunchJavaSelected.Version.Revision >= 200 AndAlso McLaunchJavaSelected.Version.Revision <= 321 AndAlso
             McLaunchJavaSelected.VersionCode <= 8 AndAlso McLaunchJavaSelected.Version.Revision >= 200 AndAlso McLaunchJavaSelected.Version.Revision <= 321 AndAlso
             Not McVersionCurrent.Version.HasOptiFine AndAlso Not McVersionCurrent.Version.HasForge Then
             Not McVersionCurrent.Version.HasOptiFine AndAlso Not McVersionCurrent.Version.HasForge Then
@@ -1713,7 +1750,7 @@ NextVersion:
                 CpStrings.Add(Library.LocalPath)
                 CpStrings.Add(Library.LocalPath)
             End If
             End If
         Next
         Next
-        If OptiFineCp IsNot Nothing Then CpStrings.Insert(CpStrings.Count - 2, OptiFineCp)
+        If OptiFineCp IsNot Nothing Then CpStrings.Insert(CpStrings.Count - 2, OptiFineCp) 'OptiFine 的总是需要放到倒数第二位
         GameArguments.Add("${classpath}", Join(CpStrings.Select(Function(c) ShortenPath(c)), ";"))
         GameArguments.Add("${classpath}", Join(CpStrings.Select(Function(c) ShortenPath(c)), ";"))
 
 
         Return GameArguments
         Return GameArguments
@@ -1759,7 +1796,7 @@ NextVersion:
                             File.Delete(FilePath)
                             File.Delete(FilePath)
                         Catch ex As UnauthorizedAccessException
                         Catch ex As UnauthorizedAccessException
                             McLaunchLog("删除原 dll 访问被拒绝,这通常代表有一个 MC 正在运行,跳过解压:" & FilePath)
                             McLaunchLog("删除原 dll 访问被拒绝,这通常代表有一个 MC 正在运行,跳过解压:" & FilePath)
-                            McLaunchLog("实际的错误信息:" & GetExceptionSummary(ex))
+                            McLaunchLog("实际的错误信息:" & ex.GetDetail())
                             Exit For
                             Exit For
                         End Try
                         End Try
                     End If
                     End If
@@ -1779,8 +1816,8 @@ NextVersion:
                 File.Delete(FileName)
                 File.Delete(FileName)
             Catch ex As UnauthorizedAccessException
             Catch ex As UnauthorizedAccessException
                 McLaunchLog("删除多余文件访问被拒绝,跳过删除步骤")
                 McLaunchLog("删除多余文件访问被拒绝,跳过删除步骤")
-                McLaunchLog("实际的错误信息:" & GetExceptionSummary(ex))
-                Exit Sub
+                McLaunchLog("实际的错误信息:" & ex.GetDetail())
+                Return
             End Try
             End Try
         Next
         Next
 
 
@@ -1790,9 +1827,9 @@ NextVersion:
     ''' </summary>
     ''' </summary>
     Private Function GetNativesFolder() As String
     Private Function GetNativesFolder() As String
         Dim Result As String = McVersionCurrent.Path & McVersionCurrent.Name & "-natives"
         Dim Result As String = McVersionCurrent.Path & McVersionCurrent.Name & "-natives"
-        If IsGBKEncoding OrElse Result.IsASCII() Then Return Result
+        If IsGBKEncoding OrElse Result.IsASCII() Then Return ShortenPath(Result)
         Result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\bin\natives"
         Result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\bin\natives"
-        If Result.IsASCII() Then Return Result
+        If Result.IsASCII() Then Return ShortenPath(Result)
         Return OsDrive & "ProgramData\PCL\natives"
         Return OsDrive & "ProgramData\PCL\natives"
     End Function
     End Function
 
 
@@ -1805,7 +1842,7 @@ NextVersion:
         '要求 Java 使用高性能显卡
         '要求 Java 使用高性能显卡
         If Setup.Get("LaunchAdvanceGraphicCard") Then
         If Setup.Get("LaunchAdvanceGraphicCard") Then
             Try
             Try
-                SetGPUPreference(McLaunchJavaSelected.PathJavaw)
+                SetGPUPreference(McLaunchJavaSelected.PathJava)
                 SetGPUPreference(PathWithName)
                 SetGPUPreference(PathWithName)
             Catch ex As Exception
             Catch ex As Exception
                 If IsAdmin() Then
                 If IsAdmin() Then
@@ -1813,7 +1850,7 @@ NextVersion:
                 Else
                 Else
                     Log(ex, "直接调整显卡设置失败,将以管理员权限重启 PCL 再次尝试")
                     Log(ex, "直接调整显卡设置失败,将以管理员权限重启 PCL 再次尝试")
                     Try
                     Try
-                        If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJavaw}""") = ProcessReturnValues.TaskDone Then
+                        If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJava}""") = ProcessReturnValues.TaskDone Then
                             McLaunchLog("以管理员权限重启 PCL 并调整显卡设置成功")
                             McLaunchLog("以管理员权限重启 PCL 并调整显卡设置成功")
                         Else
                         Else
                             Throw New Exception("调整过程中出现异常")
                             Throw New Exception("调整过程中出现异常")
@@ -2099,21 +2136,20 @@ IgnoreCustomSkin:
                 "@echo off" & vbCrLf &
                 "@echo off" & vbCrLf &
                 $"title 启动 - {McVersionCurrent.Name}" & vbCrLf &
                 $"title 启动 - {McVersionCurrent.Name}" & vbCrLf &
                 "echo 游戏正在启动,请稍候。" & vbCrLf &
                 "echo 游戏正在启动,请稍候。" & vbCrLf &
-                $"set APPDATA=""{ShortenPath(McVersionCurrent.PathIndie)}""" & vbCrLf &
                 $"cd /D ""{ShortenPath(McVersionCurrent.PathIndie)}""" & vbCrLf &
                 $"cd /D ""{ShortenPath(McVersionCurrent.PathIndie)}""" & vbCrLf &
                 CustomCommandGlobal & vbCrLf &
                 CustomCommandGlobal & vbCrLf &
                 CustomCommandVersion & vbCrLf &
                 CustomCommandVersion & vbCrLf &
                 $"""{McLaunchJavaSelected.PathJava}"" {McLaunchArgument}" & vbCrLf &
                 $"""{McLaunchJavaSelected.PathJava}"" {McLaunchArgument}" & vbCrLf &
                 "echo 游戏已退出。" & vbCrLf &
                 "echo 游戏已退出。" & vbCrLf &
                 "pause"
                 "pause"
-            WriteFile(If(CurrentLaunchOptions.SaveBatch, Path & "PCL\LatestLaunch.bat"), SecretFilter(CmdString, "F"),
-                      Encoding:=If(Encoding.Default.Equals(Encoding.UTF8), Encoding.UTF8, Encoding.GetEncoding("GB18030")))
+            WriteFile(If(CurrentLaunchOptions.SaveBatch, Path & "PCL\LatestLaunch.bat"), FilterAccessToken(CmdString, "F"),
+                      Encoding:=If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Encoding.Default))
             If CurrentLaunchOptions.SaveBatch IsNot Nothing Then
             If CurrentLaunchOptions.SaveBatch IsNot Nothing Then
                 McLaunchLog("导出启动脚本完成,强制结束启动过程")
                 McLaunchLog("导出启动脚本完成,强制结束启动过程")
                 AbortHint = "导出启动脚本成功!"
                 AbortHint = "导出启动脚本成功!"
                 OpenExplorer(CurrentLaunchOptions.SaveBatch)
                 OpenExplorer(CurrentLaunchOptions.SaveBatch)
                 Loader.Parent.Abort()
                 Loader.Parent.Abort()
-                Exit Sub '导出脚本完成
+                Return '导出脚本完成
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "输出启动脚本失败")
             Log(ex, "输出启动脚本失败")
@@ -2123,14 +2159,15 @@ IgnoreCustomSkin:
         '执行自定义命令
         '执行自定义命令
         If CustomCommandGlobal <> "" Then
         If CustomCommandGlobal <> "" Then
             McLaunchLog("正在执行全局自定义命令:" & CustomCommandGlobal)
             McLaunchLog("正在执行全局自定义命令:" & CustomCommandGlobal)
-            Dim CustomProcess As New Process
+            Dim CustomProcess As Process = Nothing
             Try
             Try
-                CustomProcess.StartInfo.FileName = "cmd.exe"
-                CustomProcess.StartInfo.Arguments = "/c """ & CustomCommandGlobal & """"
-                CustomProcess.StartInfo.WorkingDirectory = ShortenPath(PathMcFolder)
-                CustomProcess.StartInfo.UseShellExecute = False
-                CustomProcess.StartInfo.CreateNoWindow = True
-                CustomProcess.Start()
+                CustomProcess = StartProcess(New ProcessStartInfo With {
+                    .FileName = "cmd.exe",
+                    .Arguments = $"/c ""{CustomCommandGlobal}""",
+                    .WorkingDirectory = PathMcFolder,
+                    .UseShellExecute = False,
+                    .CreateNoWindow = True
+                })
                 If Setup.Get("LaunchAdvanceRunWait") Then
                 If Setup.Get("LaunchAdvanceRunWait") Then
                     Do Until CustomProcess.HasExited OrElse Loader.IsAborted
                     Do Until CustomProcess.HasExited OrElse Loader.IsAborted
                         Thread.Sleep(10)
                         Thread.Sleep(10)
@@ -2139,7 +2176,7 @@ IgnoreCustomSkin:
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "执行全局自定义命令失败", LogLevel.Hint)
                 Log(ex, "执行全局自定义命令失败", LogLevel.Hint)
             Finally
             Finally
-                If Not CustomProcess.HasExited AndAlso Loader.IsAborted Then
+                If CustomProcess IsNot Nothing AndAlso Not CustomProcess.HasExited AndAlso Loader.IsAborted Then
                     McLaunchLog("由于取消启动,已强制结束自定义命令 CMD 进程") '#1183
                     McLaunchLog("由于取消启动,已强制结束自定义命令 CMD 进程") '#1183
                     CustomProcess.Kill()
                     CustomProcess.Kill()
                 End If
                 End If
@@ -2147,14 +2184,15 @@ IgnoreCustomSkin:
         End If
         End If
         If CustomCommandVersion <> "" Then
         If CustomCommandVersion <> "" Then
             McLaunchLog("正在执行版本自定义命令:" & CustomCommandVersion)
             McLaunchLog("正在执行版本自定义命令:" & CustomCommandVersion)
-            Dim CustomProcess As New Process
+            Dim CustomProcess As Process = Nothing
             Try
             Try
-                CustomProcess.StartInfo.FileName = "cmd.exe"
-                CustomProcess.StartInfo.Arguments = "/c """ & CustomCommandVersion & """"
-                CustomProcess.StartInfo.WorkingDirectory = ShortenPath(PathMcFolder)
-                CustomProcess.StartInfo.UseShellExecute = False
-                CustomProcess.StartInfo.CreateNoWindow = True
-                CustomProcess.Start()
+                CustomProcess = StartProcess(New ProcessStartInfo With {
+                    .FileName = "cmd.exe",
+                    .Arguments = $"/c ""{CustomCommandVersion}""",
+                    .WorkingDirectory = PathMcFolder,
+                    .UseShellExecute = False,
+                    .CreateNoWindow = True
+                })
                 If Setup.Get("VersionAdvanceRunWait", Version:=McVersionCurrent) Then
                 If Setup.Get("VersionAdvanceRunWait", Version:=McVersionCurrent) Then
                     Do Until CustomProcess.HasExited OrElse Loader.IsAborted
                     Do Until CustomProcess.HasExited OrElse Loader.IsAborted
                         Thread.Sleep(10)
                         Thread.Sleep(10)
@@ -2163,7 +2201,7 @@ IgnoreCustomSkin:
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "执行版本自定义命令失败", LogLevel.Hint)
                 Log(ex, "执行版本自定义命令失败", LogLevel.Hint)
             Finally
             Finally
-                If Not CustomProcess.HasExited AndAlso Loader.IsAborted Then
+                If CustomProcess IsNot Nothing AndAlso Not CustomProcess.HasExited AndAlso Loader.IsAborted Then
                     McLaunchLog("由于取消启动,已强制结束自定义命令 CMD 进程") '#1183
                     McLaunchLog("由于取消启动,已强制结束自定义命令 CMD 进程") '#1183
                     CustomProcess.Kill()
                     CustomProcess.Kill()
                 End If
                 End If
@@ -2174,8 +2212,7 @@ IgnoreCustomSkin:
     Private Sub McLaunchRun(Loader As LoaderTask(Of Integer, Process))
     Private Sub McLaunchRun(Loader As LoaderTask(Of Integer, Process))
 
 
         '启动信息
         '启动信息
-        Dim GameProcess = New Process()
-        Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJavaw)
+        Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJava) '使用 javaw.exe 会导致 #6263
 
 
         '设置环境变量
         '设置环境变量
         Dim Paths As New List(Of String)(StartInfo.EnvironmentVariables("Path").Split(";"))
         Dim Paths As New List(Of String)(StartInfo.EnvironmentVariables("Path").Split(";"))
@@ -2184,23 +2221,20 @@ IgnoreCustomSkin:
         StartInfo.EnvironmentVariables("appdata") = ShortenPath(PathMcFolder)
         StartInfo.EnvironmentVariables("appdata") = ShortenPath(PathMcFolder)
 
 
         '设置其他参数
         '设置其他参数
-        StartInfo.StandardErrorEncoding = If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Nothing)
-        StartInfo.StandardOutputEncoding = If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Nothing)
-        StartInfo.WorkingDirectory = ShortenPath(McVersionCurrent.PathIndie)
+        StartInfo.WorkingDirectory = McVersionCurrent.PathIndie
         StartInfo.UseShellExecute = False
         StartInfo.UseShellExecute = False
         StartInfo.RedirectStandardOutput = True
         StartInfo.RedirectStandardOutput = True
         StartInfo.RedirectStandardError = True
         StartInfo.RedirectStandardError = True
-        StartInfo.CreateNoWindow = False
+        StartInfo.CreateNoWindow = True
         StartInfo.Arguments = McLaunchArgument
         StartInfo.Arguments = McLaunchArgument
-        GameProcess.StartInfo = StartInfo
 
 
         '开始进程
         '开始进程
-        GameProcess.Start()
-        McLaunchLog("已启动游戏进程:" & McLaunchJavaSelected.PathJavaw)
+        Dim GameProcess = StartProcess(StartInfo)
+        McLaunchLog("已启动游戏进程:" & McLaunchJavaSelected.PathJava)
         If Loader.IsAborted Then
         If Loader.IsAborted Then
             McLaunchLog("由于取消启动,已强制结束游戏进程") '#1631
             McLaunchLog("由于取消启动,已强制结束游戏进程") '#1631
             GameProcess.Kill()
             GameProcess.Kill()
-            Exit Sub
+            Return
         End If
         End If
         Loader.Output = GameProcess
         Loader.Output = GameProcess
         McLaunchProcess = GameProcess
         McLaunchProcess = GameProcess

+ 209 - 200
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -86,7 +86,7 @@ Public Module ModMinecraft
                     Next
                     Next
                     If Not Renamed Then CacheMcFolderList.Add(New McFolder With {.Name = Name, .Path = Path, .Type = McFolderType.Custom})
                     If Not Renamed Then CacheMcFolderList.Add(New McFolder With {.Name = Name, .Path = Path, .Type = McFolderType.Custom})
                 Catch ex As Exception
                 Catch ex As Exception
-                    MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & GetExceptionSummary(ex), "Minecraft 文件夹失效", IsWarn:=True)
+                    MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & ex.GetBrief(), "Minecraft 文件夹失效", IsWarn:=True)
                     Log(ex, $"无法访问 Minecraft 文件夹 {Path}")
                     Log(ex, $"无法访问 Minecraft 文件夹 {Path}")
                 End Try
                 End Try
             Next
             Next
@@ -127,7 +127,7 @@ Public Module ModMinecraft
     ''' </summary>
     ''' </summary>
     Public Sub McFolderLauncherProfilesJsonCreate(Folder As String)
     Public Sub McFolderLauncherProfilesJsonCreate(Folder As String)
         Try
         Try
-            If File.Exists(Folder & "launcher_profiles.json") Then Exit Sub
+            If File.Exists(Folder & "launcher_profiles.json") Then Return
             Dim ResultJson As String =
             Dim ResultJson As String =
 "{
 "{
     ""profiles"":  {
     ""profiles"":  {
@@ -165,12 +165,12 @@ Public Module ModMinecraft
             Return _McVersionCurrent
             Return _McVersionCurrent
         End Get
         End Get
         Set(value As McVersion)
         Set(value As McVersion)
-            If ReferenceEquals(_McVersionLast, value) Then Exit Property
+            If ReferenceEquals(_McVersionLast, value) Then Return
             _McVersionCurrent = value '由于有可能是 Nothing,导致无法初始化,才得这样弄一圈
             _McVersionCurrent = value '由于有可能是 Nothing,导致无法初始化,才得这样弄一圈
             _McVersionLast = value
             _McVersionLast = value
-            If value Is Nothing Then Exit Property
-            '重置缓存的 Mod 文件夹
-            PageDownloadCompDetail.CachedFolder = Nothing
+            If value Is Nothing Then Return
+            '重置缓存的下载文件夹
+            PageDownloadCompDetail.CachedFolder.Clear()
             '统一通行证重判
             '统一通行证重判
             If AniControlEnabled = 0 AndAlso
             If AniControlEnabled = 0 AndAlso
                Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso
                Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso
@@ -207,46 +207,44 @@ Public Module ModMinecraft
         ''' </summary>
         ''' </summary>
         Public ReadOnly Property PathIndie As String
         Public ReadOnly Property PathIndie As String
             Get
             Get
-                InitPathIndie(Modable)
+                If Setup.IsUnset("VersionArgumentIndieV2", Version:=Me) Then
+                    If Not IsLoaded Then Load()
+                    '决定该版本是否应该被隔离
+                    Dim ShouldBeIndie =
+                    Function() As Boolean
+                        '从老的版本独立设置中迁移:-1 未决定,0 使用全局设置,1 手动开启,2 手动关闭
+                        If Not Setup.IsUnset("VersionArgumentIndie", Version:=Me) AndAlso Setup.Get("VersionArgumentIndie", Version:=Me) > 0 Then
+                            Log($"[Minecraft] 版本隔离初始化({Name}):从老的版本独立设置中迁移")
+                            Return Setup.Get("VersionArgumentIndie", Version:=Me) = 1
+                        End If
+                        '若版本文件夹下包含 mods 或 saves 文件夹,则自动开启版本隔离
+                        Dim ModFolder As New DirectoryInfo(Path & "mods\")
+                        Dim SaveFolder As New DirectoryInfo(Path & "saves\")
+                        If (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Any) OrElse (SaveFolder.Exists AndAlso SaveFolder.EnumerateDirectories.Any) Then
+                            Log($"[Minecraft] 版本隔离初始化({Name}):版本文件夹下存在 mods 或 saves 文件夹,自动开启")
+                            Return True
+                        End If
+                        '根据全局的默认设置决定是否隔离
+                        Dim IsRelease As Boolean = State <> McVersionState.Fool AndAlso State <> McVersionState.Old AndAlso State <> McVersionState.Snapshot
+                        Log($"[Minecraft] 版本隔离初始化({Name}):从全局默认设置中({Setup.Get("LaunchArgumentIndieV2")})判断,State {GetStringFromEnum(State)},IsRelease {IsRelease},Modable {Modable}")
+                        Select Case Setup.Get("LaunchArgumentIndieV2")
+                            Case 0 '关闭
+                                Return False
+                            Case 1 '仅隔离可安装 Mod 的版本
+                                Return Modable
+                            Case 2 '仅隔离非正式版
+                                Return Not IsRelease
+                            Case 3 '隔离非正式版与可安装 Mod 的版本
+                                Return Not IsRelease OrElse Modable
+                            Case Else '隔离所有版本
+                                Return True
+                        End Select
+                    End Function
+                    Setup.Set("VersionArgumentIndieV2", ShouldBeIndie(), Version:=Me)
+                End If
                 Return If(Setup.Get("VersionArgumentIndieV2", Version:=Me), Path, PathMcFolder)
                 Return If(Setup.Get("VersionArgumentIndieV2", Version:=Me), Path, PathMcFolder)
             End Get
             End Get
         End Property
         End Property
-        ''' <summary>
-        ''' 决定该版本是否应该被隔离。
-        ''' </summary>
-        Public Sub InitPathIndie(Modable As Boolean)
-            If Not Setup.IsUnset("VersionArgumentIndieV2", Version:=Me) Then Return
-            Dim ShouldBeIndie =
-            Function() As Boolean
-                '从老的版本独立设置中迁移:-1 未决定,0 使用全局设置,1 手动开启,2 手动关闭
-                If Not Setup.IsUnset("VersionArgumentIndie", Version:=Me) AndAlso Setup.Get("VersionArgumentIndie", Version:=Me) > 0 Then
-                    Log($"[Minecraft] 版本隔离初始化({Name}):从老的版本独立设置中迁移")
-                    Return Setup.Get("VersionArgumentIndie", Version:=Me) = 1
-                End If
-                '若版本文件夹下包含 mods 或 saves 文件夹,则自动开启版本隔离
-                Dim ModFolder As New DirectoryInfo(Path & "mods\")
-                Dim SaveFolder As New DirectoryInfo(Path & "saves\")
-                If (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Any) OrElse (SaveFolder.Exists AndAlso SaveFolder.EnumerateFiles.Any) Then
-                    Log($"[Minecraft] 版本隔离初始化({Name}):版本文件夹下存在 mods 或 saves 文件夹,自动开启")
-                    Return True
-                End If
-                '根据全局的默认设置决定是否隔离
-                Log($"[Minecraft] 版本隔离初始化({Name}):从全局默认设置中({Setup.Get("LaunchArgumentIndieV2")})判断")
-                Select Case Setup.Get("LaunchArgumentIndieV2")
-                    Case 0 '关闭
-                        Return False
-                    Case 1 '仅隔离可安装 Mod 的版本
-                        Return Modable
-                    Case 2 '仅隔离非正式版
-                        Return State = McVersionState.Fool OrElse State = McVersionState.Old OrElse State = McVersionState.Snapshot
-                    Case 3 '隔离非正式版与可安装 Mod 的版本
-                        Return Modable OrElse State = McVersionState.Fool OrElse State = McVersionState.Old OrElse State = McVersionState.Snapshot
-                    Case Else '隔离所有版本
-                        Return True
-                End Select
-            End Function
-            Setup.Set("VersionArgumentIndieV2", ShouldBeIndie(), Version:=Me)
-        End Sub
 
 
         ''' <summary>
         ''' <summary>
         ''' 该版本的版本文件夹名称。
         ''' 该版本的版本文件夹名称。
@@ -318,14 +316,6 @@ Public Module ModMinecraft
                             _Version.McName = "pending"
                             _Version.McName = "pending"
                             GoTo VersionSearchFinish
                             GoTo VersionSearchFinish
                         End If
                         End If
-                        '从 JumpLoader 信息中获取版本号
-                        If HasJumpLoader Then
-                            Try
-                                _Version.McName = JsonObject("jumploader")("jars")("minecraft")(0)("gameVersion")
-                                GoTo VersionSearchFinish
-                            Catch
-                            End Try
-                        End If
                         '从 PCL 下载的版本信息中获取版本号
                         '从 PCL 下载的版本信息中获取版本号
                         If JsonObject("clientVersion") IsNot Nothing Then
                         If JsonObject("clientVersion") IsNot Nothing Then
                             _Version.McName = JsonObject("clientVersion")
                             _Version.McName = JsonObject("clientVersion")
@@ -382,7 +372,6 @@ Public Module ModMinecraft
                             _Version.McName = Regex
                             _Version.McName = Regex
                             GoTo VersionSearchFinish
                             GoTo VersionSearchFinish
                         End If
                         End If
-                        'FUTURE: [Quilt 支持] 从 Quilt 版本中获取版本号
                         '从 jar 项中获取版本号
                         '从 jar 项中获取版本号
                         If JsonObject("jar") IsNot Nothing Then
                         If JsonObject("jar") IsNot Nothing Then
                             _Version.McName = JsonObject("jar").ToString
                             _Version.McName = JsonObject("jar").ToString
@@ -432,7 +421,7 @@ VersionSearchFinish:
                         _Version.McCodeMain = If(SplitResult.Length <= 2, Val(SplitResult), "0")
                         _Version.McCodeMain = If(SplitResult.Length <= 2, Val(SplitResult), "0")
                         SplitResult = If(SplitVersion.Count >= 3, SplitVersion(2), "0")
                         SplitResult = If(SplitVersion.Count >= 3, SplitVersion(2), "0")
                         _Version.McCodeSub = If(SplitResult.Length <= 2, Val(SplitResult), "0")
                         _Version.McCodeSub = If(SplitResult.Length <= 2, Val(SplitResult), "0")
-                    ElseIf _Version.McName.Contains("w") OrElse _Version.McName = "pending" Then
+                    ElseIf (Not IsVersionNameLikeRelease(_Version.McName)) OrElse _Version.McName = "pending" Then
                         _Version.McCodeMain = 99
                         _Version.McCodeMain = 99
                         _Version.McCodeSub = 99
                         _Version.McCodeSub = 99
                     End If
                     End If
@@ -451,6 +440,33 @@ VersionSearchFinish:
         ''' </summary>
         ''' </summary>
         Public ReleaseTime As New Date(1970, 1, 1, 15, 0, 0)
         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>
         ''' <summary>
         ''' 该版本的 Json 文本。
         ''' 该版本的 Json 文本。
         ''' </summary>
         ''' </summary>
@@ -463,17 +479,7 @@ VersionSearchFinish:
                     Return TrimedJson.StartsWithF("{") AndAlso TrimedJson.EndsWithF("}")
                     Return TrimedJson.StartsWithF("{") AndAlso TrimedJson.EndsWithF("}")
                 End Function
                 End Function
                 If _JsonText Is Nothing Then
                 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)
                     _JsonText = ReadFile(JsonPath)
                     '如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试
                     '如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试
                     If Not FastJsonCheck(_JsonText) Then
                     If Not FastJsonCheck(_JsonText) Then
@@ -551,9 +557,15 @@ Recheck:
                                 '继续循环
                                 '继续循环
                                 If Inherit.InheritVersion = InheritVersion Then Throw New Exception("版本依赖项出现嵌套:" & InheritVersion)
                                 If Inherit.InheritVersion = InheritVersion Then Throw New Exception("版本依赖项出现嵌套:" & InheritVersion)
                                 InheritVersion = Inherit.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)
                                 Inherit.JsonObject.Merge(_JsonObject)
                                 _JsonObject = Inherit.JsonObject
                                 _JsonObject = Inherit.JsonObject
+                                _JsonObject("libraries") = CurrentLib
                                 GoTo Recheck
                                 GoTo Recheck
                             End If
                             End If
                         Catch ex As Exception
                         Catch ex As Exception
@@ -562,25 +574,6 @@ Recheck:
                     Catch ex As Exception
                     Catch ex As Exception
                         Throw New Exception("初始化版本 JSON 时失败(" & If(Name, "null") & ")", ex)
                         Throw New Exception("初始化版本 JSON 时失败(" & If(Name, "null") & ")", ex)
                     End Try
                     End Try
-                    Try
-                        '处理 JumpLoader
-                        If Text.Contains("minecraftforge") AndAlso File.Exists(PathIndie & "config\jumploader.json") Then
-                            For Each ModFile In Directory.EnumerateFiles(PathIndie & "mods")
-                                Dim FileName As String = GetFileNameFromPath(ModFile)
-                                If FileName.EndsWithF(".jar", True) AndAlso FileName.ContainsF("jumploader", True) Then
-                                    Log("[Minecraft] 发现 JumpLoader 分支项:" & FileName)
-                                    HasJumpLoader = True
-                                    Exit For
-                                End If
-                            Next
-                        End If
-                        If HasJumpLoader Then
-                            _JsonObject.Remove("jumploader")
-                            _JsonObject.Add("jumploader", GetJson(ReadFile(PathIndie & "config\jumploader.json")))
-                        End If
-                    Catch ex As Exception
-                        Log(ex, "处理 JumpLoader 失败")
-                    End Try
                 End If
                 End If
                 Return _JsonObject
                 Return _JsonObject
             End Get
             End Get
@@ -601,10 +594,6 @@ Recheck:
         ''' Json 是否为 HMCL 格式。
         ''' Json 是否为 HMCL 格式。
         ''' </summary>
         ''' </summary>
         Public Property IsHmclFormatJson As Boolean = False
         Public Property IsHmclFormatJson As Boolean = False
-        ''' <summary>
-        ''' 是否包含 JumpLoader。
-        ''' </summary>
-        Public Property HasJumpLoader As Boolean = False
 
 
         ''' <summary>
         ''' <summary>
         ''' 版本 jar 中的 version.json 文件对象。
         ''' 版本 jar 中的 version.json 文件对象。
@@ -614,20 +603,18 @@ Recheck:
             Get
             Get
                 If Not JsonVersionInited Then
                 If Not JsonVersionInited Then
                     JsonVersionInited = True
                     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
                             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
                 End If
                 Return _JsonVersion
                 Return _JsonVersion
             End Get
             End Get
@@ -708,7 +695,7 @@ Recheck:
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "依赖版本检查出错(" & Name & ")")
                 Log(ex, "依赖版本检查出错(" & Name & ")")
                 State = McVersionState.Error
                 State = McVersionState.Error
-                Info = "未知错误:" & GetExceptionSummary(ex)
+                Info = "未知错误:" & ex.GetBrief()
                 Return False
                 Return False
             End Try
             End Try
 
 
@@ -748,7 +735,6 @@ Recheck:
                             Version.HasLiteLoader = True
                             Version.HasLiteLoader = True
                         End If
                         End If
                         'Fabric、Forge
                         'Fabric、Forge
-                        'FUTURE: [Quilt 支持] 确认这里的玩意儿对不对
                         If RealJson.Contains("net.fabricmc:fabric-loader") OrElse RealJson.Contains("org.quiltmc:quilt-loader") Then
                         If RealJson.Contains("net.fabricmc:fabric-loader") OrElse RealJson.Contains("org.quiltmc:quilt-loader") Then
                             State = McVersionState.Fabric
                             State = McVersionState.Fabric
                             Version.HasFabric = True
                             Version.HasFabric = True
@@ -822,7 +808,7 @@ ExitDataLoad:
                     WriteIni(Path & "PCL\Setup.ini", "VersionOriginalSub", Version.McCodeSub)
                     WriteIni(Path & "PCL\Setup.ini", "VersionOriginalSub", Version.McCodeSub)
                 End If
                 End If
             Catch ex As Exception
             Catch ex As Exception
-                Info = "未知错误:" & GetExceptionSummary(ex)
+                Info = "未知错误:" & ex.GetBrief()
                 Logo = PathImage & "Blocks/RedstoneBlock.png"
                 Logo = PathImage & "Blocks/RedstoneBlock.png"
                 State = McVersionState.Error
                 State = McVersionState.Error
                 Log(ex, "加载版本失败(" & Name & ")", LogLevel.Feedback)
                 Log(ex, "加载版本失败(" & Name & ")", LogLevel.Feedback)
@@ -859,7 +845,6 @@ ExitDataLoad:
                     Info = "发生了未知错误,请向作者反馈此问题"
                     Info = "发生了未知错误,请向作者反馈此问题"
             End Select
             End Select
             If Not State = McVersionState.Error Then
             If Not State = McVersionState.Error Then
-                If HasJumpLoader Then Info += ", JumpLoader"
                 If Setup.Get("VersionServerLogin", Version:=Me) = 3 Then Info += ", 统一通行证验证"
                 If Setup.Get("VersionServerLogin", Version:=Me) = 3 Then Info += ", 统一通行证验证"
                 If Setup.Get("VersionServerLogin", Version:=Me) = 4 Then Info += ", Authlib 验证"
                 If Setup.Get("VersionServerLogin", Version:=Me) = 4 Then Info += ", Authlib 验证"
             End If
             End If
@@ -919,6 +904,24 @@ ExitDataLoad:
         ''' 原版次版本号,如 2(For 1.12.2),快照则固定为 99。不可用则为 -1。
         ''' 原版次版本号,如 2(For 1.12.2),快照则固定为 99。不可用则为 -1。
         ''' </summary>
         ''' </summary>
         Public McCodeSub As Integer = -1
         Public McCodeSub As Integer = -1
+        ''' <summary>
+        ''' 是否为非快照版,且读取到了一个有效的原版版本号。
+        ''' </summary>
+        Public ReadOnly Property IsStandardVersion As Boolean
+            Get
+                Return McCodeMain > -1 AndAlso McCodeMain < 99 AndAlso McCodeSub > -1 AndAlso McCodeSub < 99
+            End Get
+        End Property
+        ''' <summary>
+        ''' 标准的原版版本号。
+        ''' 若为快照版或没有有效版本号,则返回 0。
+        ''' </summary>
+        Public ReadOnly Property McVersion As Version
+            Get
+                If Not IsStandardVersion Then Return New Version(0, 0, 0)
+                Return New Version(1, McCodeMain, McCodeSub)
+            End Get
+        End Property
 
 
         'OptiFine
         'OptiFine
 
 
@@ -986,7 +989,7 @@ ExitDataLoad:
             If ToString = "" Then
             If ToString = "" Then
                 Return "原版 " & McName
                 Return "原版 " & McName
             Else
             Else
-                Return McName & ToString & If(ModeDebug, " (" & SortCode & "#)", "")
+                Return McName & ToString
             End If
             End If
         End Function
         End Function
 
 
@@ -1152,7 +1155,7 @@ Reload:
 
 
             '改变当前选择的版本
             '改变当前选择的版本
 OnLoaded:
 OnLoaded:
-            If Loader.IsAborted Then Exit Sub
+            If Loader.IsAborted Then Return
             If McVersionList.Any(Function(v) v.Key <> McVersionCardType.Error) Then
             If McVersionList.Any(Function(v) v.Key <> McVersionCardType.Error) Then
                 '尝试读取已储存的选择
                 '尝试读取已储存的选择
                 Dim SavedSelection As String = ReadIni(Path & "PCL.ini", "Version")
                 Dim SavedSelection As String = ReadIni(Path & "PCL.ini", "Version")
@@ -1164,7 +1167,7 @@ OnLoaded:
                                 McVersionCurrent = Version
                                 McVersionCurrent = Version
                                 Setup.Set("LaunchVersionSelect", McVersionCurrent.Name)
                                 Setup.Set("LaunchVersionSelect", McVersionCurrent.Name)
                                 Log("[Minecraft] 选择该文件夹储存的 Minecraft 版本:" & McVersionCurrent.Path)
                                 Log("[Minecraft] 选择该文件夹储存的 Minecraft 版本:" & McVersionCurrent.Path)
-                                Exit Sub
+                                Return
                             End If
                             End If
                         Next
                         Next
                     Next
                     Next
@@ -1183,6 +1186,7 @@ OnLoaded:
             If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(200, 3000))
             If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(200, 3000))
         Catch ex As ThreadInterruptedException
         Catch ex As ThreadInterruptedException
         Catch ex As Exception
         Catch ex As Exception
+            If Loader.IsAborted Then Return '#5617
             WriteIni(Path & "PCL.ini", "VersionCache", "") '要求下次重新加载
             WriteIni(Path & "PCL.ini", "VersionCache", "") '要求下次重新加载
             Log(ex, "加载 .minecraft 版本列表失败", LogLevel.Feedback)
             Log(ex, "加载 .minecraft 版本列表失败", LogLevel.Feedback)
         End Try
         End Try
@@ -1556,7 +1560,7 @@ OnLoaded:
     ''' 要求玩家选择一个皮肤文件,并进行相关校验。
     ''' 要求玩家选择一个皮肤文件,并进行相关校验。
     ''' </summary>
     ''' </summary>
     Public Function McSkinSelect() As McSkinInfo
     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}
         If FileName = "" Then Return New McSkinInfo With {.IsVaild = False}
@@ -1589,11 +1593,11 @@ OnLoaded:
     ''' <summary>
     ''' <summary>
     ''' 获取 Uuid 对应的皮肤文件地址,失败将抛出异常。
     ''' 获取 Uuid 对应的皮肤文件地址,失败将抛出异常。
     ''' </summary>
     ''' </summary>
-    Public Function McSkinGetAddress(Uuid As String, Type As String) As String
-        If Uuid = "" Then Throw New Exception("Uuid 为空。")
-        If Uuid.StartsWithF("00000") Then Throw New Exception("离线 Uuid 无正版皮肤文件。")
+    Public Function McSkinGetAddress(UUID As String, Type As String) As String
+        If UUID = "" Then Throw New Exception("UUID 为空。")
+        If UUID.StartsWithF("00000") AndAlso Type <> "Auth" Then Throw New Exception("离线 UUID 无正版皮肤文件:" & UUID)
         '尝试读取缓存
         '尝试读取缓存
-        Dim CacheSkinAddress As String = ReadIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid)
+        Dim CacheSkinAddress As String = ReadIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", UUID)
         If Not CacheSkinAddress = "" Then Return CacheSkinAddress
         If Not CacheSkinAddress = "" Then Return CacheSkinAddress
         '获取皮肤地址
         '获取皮肤地址
         Dim Url As String
         Dim Url As String
@@ -1607,7 +1611,7 @@ OnLoaded:
             Case Else
             Case Else
                 Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null"))
                 Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null"))
         End Select
         End Select
-        Dim SkinString = NetGetCodeByRequestRetry(Url & Uuid)
+        Dim SkinString = NetRequestByClientRetry(Url & UUID, RequireJson:=True)
         If SkinString = "" Then Throw New Exception("皮肤返回值为空,可能是未设置自定义皮肤的用户")
         If SkinString = "" Then Throw New Exception("皮肤返回值为空,可能是未设置自定义皮肤的用户")
         '处理皮肤地址
         '处理皮肤地址
         Dim SkinValue As String
         Dim SkinValue As String
@@ -1628,11 +1632,12 @@ OnLoaded:
         If SkinJson("textures") Is Nothing OrElse SkinJson("textures")("skin") Is Nothing OrElse SkinJson("textures")("skin")("url") Is Nothing Then
         If SkinJson("textures") Is Nothing OrElse SkinJson("textures")("skin") Is Nothing OrElse SkinJson("textures")("skin")("url") Is Nothing Then
             Throw New Exception("用户未设置自定义皮肤")
             Throw New Exception("用户未设置自定义皮肤")
         Else
         Else
-            SkinValue = SkinJson("textures")("skin")("url").ToString
+            Dim SkinUrl As String = SkinJson("textures")("skin")("url").ToString
+            SkinValue = If(SkinUrl.Contains("minecraft.net/"), SkinUrl.Replace("http://", "https://"), SkinUrl)
         End If
         End If
         '保存缓存
         '保存缓存
-        WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid, SkinValue)
-        Log("[Skin] UUID " & Uuid & " 对应的皮肤文件为 " & SkinValue)
+        WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", UUID, SkinValue)
+        Log("[Skin] UUID " & UUID & " 对应的皮肤文件为 " & SkinValue)
         Return SkinValue
         Return SkinValue
     End Function
     End Function
 
 
@@ -1645,9 +1650,9 @@ OnLoaded:
         Dim FileAddress As String = PathTemp & "Cache\Skin\" & GetHash(Address) & ".png"
         Dim FileAddress As String = PathTemp & "Cache\Skin\" & GetHash(Address) & ".png"
         SyncLock McSkinDownloadLock
         SyncLock McSkinDownloadLock
             If Not File.Exists(FileAddress) Then
             If Not File.Exists(FileAddress) Then
-                NetDownloadByClient(Address, FileAddress & NetDownloadEnd)
+                NetDownloadByClient(Address, FileAddress & ".PCLDownloading")
                 File.Delete(FileAddress)
                 File.Delete(FileAddress)
-                FileSystem.Rename(FileAddress & NetDownloadEnd, FileAddress)
+                FileSystem.Rename(FileAddress & ".PCLDownloading", FileAddress)
                 Log("[Minecraft] 皮肤下载成功:" & FileAddress)
                 Log("[Minecraft] 皮肤下载成功:" & FileAddress)
             End If
             End If
             Return FileAddress
             Return FileAddress
@@ -1728,10 +1733,6 @@ OnLoaded:
         ''' 原 Json 中的 Name 项。
         ''' 原 Json 中的 Name 项。
         ''' </summary>
         ''' </summary>
         Public OriginalName As String
         Public OriginalName As String
-        ''' <summary>
-        ''' 是否为 JumpLoader 项。
-        ''' </summary>
-        Public IsJumpLoader As Boolean = False
 
 
         Public Overrides Function ToString() As String
         Public Overrides Function ToString() As String
             Return If(IsNatives, "[Native] ", "") & GetString(Size) & " | " & LocalPath
             Return If(IsNatives, "[Native] ", "") & GetString(Size) & " | " & LocalPath
@@ -1794,7 +1795,7 @@ OnLoaded:
 
 
         '获取当前支持库列表
         '获取当前支持库列表
         Log("[Minecraft] 获取支持库列表:" & Version.Name)
         Log("[Minecraft] 获取支持库列表:" & Version.Name)
-        McLibListGet = McLibListGetWithJson(Version.JsonObject, JumpLoaderFolder:=Version.PathIndie & ".jumploader\")
+        Dim Result = McLibListGetWithJson(Version.JsonObject)
 
 
         '需要添加原版 Jar
         '需要添加原版 Jar
         If IncludeVersionJar Then
         If IncludeVersionJar Then
@@ -1834,28 +1835,20 @@ OnLoaded:
                 ClientSHA1 = Nothing
                 ClientSHA1 = Nothing
             End If
             End If
             '把所需的原版 Jar 添加进去
             '把所需的原版 Jar 添加进去
-            McLibListGet.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1})
+            Result.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1})
         End If
         End If
 
 
+        Return Result
     End Function
     End Function
     ''' <summary>
     ''' <summary>
     ''' 获取 Minecraft 某一版本忽视继承的支持库列表,即结果中没有继承项。
     ''' 获取 Minecraft 某一版本忽视继承的支持库列表,即结果中没有继承项。
     ''' </summary>
     ''' </summary>
-    Public Function McLibListGetWithJson(JsonObject As JObject, Optional KeepSameNameDifferentVersionResult As Boolean = False, Optional CustomMcFolder As String = Nothing, Optional JumpLoaderFolder As String = Nothing) As List(Of McLibToken)
+    Public Function McLibListGetWithJson(JsonObject As JObject, Optional KeepSameNameDifferentVersionResult As Boolean = False, Optional CustomMcFolder As String = Nothing) As List(Of McLibToken)
         CustomMcFolder = If(CustomMcFolder, PathMcFolder)
         CustomMcFolder = If(CustomMcFolder, PathMcFolder)
-        Dim BasicArray As New List(Of McLibToken)
-
-        '添加基础 Json 项
-        Dim AllLibs As JArray = JsonObject("libraries")
-        '添加 JumpLoader Json 项
-        If JsonObject("jumploader") IsNot Nothing AndAlso JsonObject("jumploader")("jars") IsNot Nothing AndAlso JsonObject("jumploader")("jars")("maven") IsNot Nothing Then
-            For Each JumpLoaderToken In JsonObject("jumploader")("jars")("maven")
-                AllLibs.Add(JumpLoaderToken)
-            Next
-        End If
 
 
         '转换为 LibToken
         '转换为 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)
             '清理 null 项(BakaXL 会把没有的项序列化为 null,但会被 Newtonsoft 转换为 JValue,导致 Is Nothing = false;这导致了 #409)
             For i = Library.Properties.Count - 1 To 0 Step -1
             For i = Library.Properties.Count - 1 To 0 Step -1
@@ -1865,14 +1858,6 @@ OnLoaded:
             '检查是否需要(Rules)
             '检查是否需要(Rules)
             If Not McJsonRuleCheck(Library("rules")) Then Continue For
             If Not McJsonRuleCheck(Library("rules")) Then Continue For
 
 
-            '检查 JumpLoader
-            Dim IsJumpLoader As Boolean = False
-            If Library("mavenPath") IsNot Nothing Then
-                IsJumpLoader = True
-                If Library("name") Is Nothing Then Library.Add("name", Library("mavenPath")) '这里的修改会导致原 Json 内容改变
-                If Library("repoUrl") IsNot Nothing AndAlso Library("url") Is Nothing Then Library.Add("url", Library("repoUrl"))
-            End If
-
             '获取根节点下的 url
             '获取根节点下的 url
             Dim RootUrl As String = Library("url")
             Dim RootUrl As String = Library("url")
             If RootUrl IsNot Nothing Then
             If RootUrl IsNot Nothing Then
@@ -1882,15 +1867,10 @@ OnLoaded:
             '根据是否本地化处理(Natives)
             '根据是否本地化处理(Natives)
             If Library("natives") Is Nothing Then '没有 Natives
             If Library("natives") Is Nothing Then '没有 Natives
                 Dim LocalPath As String
                 Dim LocalPath As String
-                If IsJumpLoader Then
-                    LocalPath = McLibGet(Library("name"), CustomMcFolder:=If(JumpLoaderFolder, CustomMcFolder))
-                Else
-                    LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder)
-                End If
+                LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder)
                 Try
                 Try
                     If Library("downloads") IsNot Nothing AndAlso Library("downloads")("artifact") IsNot Nothing Then
                     If Library("downloads") IsNot Nothing AndAlso Library("downloads")("artifact") IsNot Nothing Then
                         BasicArray.Add(New McLibToken With {
                         BasicArray.Add(New McLibToken With {
-                            .IsJumpLoader = IsJumpLoader,
                             .OriginalName = Library("name"),
                             .OriginalName = Library("name"),
                             .Url = If(RootUrl, Library("downloads")("artifact")("url")),
                             .Url = If(RootUrl, Library("downloads")("artifact")("url")),
                             .LocalPath = If(Library("downloads")("artifact")("path") Is Nothing, McLibGet(Library("name"),
                             .LocalPath = If(Library("downloads")("artifact")("path") Is Nothing, McLibGet(Library("name"),
@@ -1899,17 +1879,16 @@ OnLoaded:
                             .IsNatives = False,
                             .IsNatives = False,
                             .SHA1 = Library("downloads")("artifact")("sha1")?.ToString})
                             .SHA1 = Library("downloads")("artifact")("sha1")?.ToString})
                     Else
                     Else
-                        BasicArray.Add(New McLibToken With {.IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = RootUrl, .LocalPath = LocalPath, .Size = 0, .IsNatives = False, .SHA1 = Nothing})
+                        BasicArray.Add(New McLibToken With {.OriginalName = Library("name"), .Url = RootUrl, .LocalPath = LocalPath, .Size = 0, .IsNatives = False, .SHA1 = Nothing})
                     End If
                     End If
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "处理实际支持库列表失败(无 Natives," & If(Library("name"), "Nothing").ToString & ")")
                     Log(ex, "处理实际支持库列表失败(无 Natives," & If(Library("name"), "Nothing").ToString & ")")
-                    BasicArray.Add(New McLibToken With {.IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = RootUrl, .LocalPath = LocalPath, .Size = 0, .IsNatives = False, .SHA1 = Nothing})
+                    BasicArray.Add(New McLibToken With {.OriginalName = Library("name"), .Url = RootUrl, .LocalPath = LocalPath, .Size = 0, .IsNatives = False, .SHA1 = Nothing})
                 End Try
                 End Try
             ElseIf Library("natives")("windows") IsNot Nothing Then '有 Windows Natives
             ElseIf Library("natives")("windows") IsNot Nothing Then '有 Windows Natives
                 Try
                 Try
                     If Library("downloads") IsNot Nothing AndAlso Library("downloads")("classifiers") IsNot Nothing AndAlso Library("downloads")("classifiers")("natives-windows") IsNot Nothing Then
                     If Library("downloads") IsNot Nothing AndAlso Library("downloads")("classifiers") IsNot Nothing AndAlso Library("downloads")("classifiers")("natives-windows") IsNot Nothing Then
                         BasicArray.Add(New McLibToken With {
                         BasicArray.Add(New McLibToken With {
-                             .IsJumpLoader = IsJumpLoader,
                              .OriginalName = Library("name"),
                              .OriginalName = Library("name"),
                              .Url = If(RootUrl, Library("downloads")("classifiers")("natives-windows")("url")),
                              .Url = If(RootUrl, Library("downloads")("classifiers")("natives-windows")("url")),
                              .LocalPath = If(Library("downloads")("classifiers")("natives-windows")("path") Is Nothing,
                              .LocalPath = If(Library("downloads")("classifiers")("natives-windows")("path") Is Nothing,
@@ -1919,11 +1898,11 @@ OnLoaded:
                              .IsNatives = True,
                              .IsNatives = True,
                              .SHA1 = Library("downloads")("classifiers")("natives-windows")("sha1").ToString})
                              .SHA1 = Library("downloads")("classifiers")("natives-windows")("sha1").ToString})
                     Else
                     Else
-                        BasicArray.Add(New McLibToken With {.IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = RootUrl, .LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder).Replace(".jar", "-" & Library("natives")("windows").ToString & ".jar").Replace("${arch}", If(Environment.Is64BitOperatingSystem, "64", "32")), .Size = 0, .IsNatives = True, .SHA1 = Nothing})
+                        BasicArray.Add(New McLibToken With {.OriginalName = Library("name"), .Url = RootUrl, .LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder).Replace(".jar", "-" & Library("natives")("windows").ToString & ".jar").Replace("${arch}", If(Environment.Is64BitOperatingSystem, "64", "32")), .Size = 0, .IsNatives = True, .SHA1 = Nothing})
                     End If
                     End If
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "处理实际支持库列表失败(有 Natives," & If(Library("name"), "Nothing").ToString & ")")
                     Log(ex, "处理实际支持库列表失败(有 Natives," & If(Library("name"), "Nothing").ToString & ")")
-                    BasicArray.Add(New McLibToken With {.IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = RootUrl, .LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder).Replace(".jar", "-" & Library("natives")("windows").ToString & ".jar").Replace("${arch}", If(Environment.Is64BitOperatingSystem, "64", "32")), .Size = 0, .IsNatives = True, .SHA1 = Nothing})
+                    BasicArray.Add(New McLibToken With {.OriginalName = Library("name"), .Url = RootUrl, .LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder).Replace(".jar", "-" & Library("natives")("windows").ToString & ".jar").Replace("${arch}", If(Environment.Is64BitOperatingSystem, "64", "32")), .Size = 0, .IsNatives = True, .SHA1 = Nothing})
                 End Try
                 End Try
             End If
             End If
 
 
@@ -1940,7 +1919,7 @@ OnLoaded:
             Return GetFolderNameFromPath(GetPathFromFullPath(Token.LocalPath))
             Return GetFolderNameFromPath(GetPathFromFullPath(Token.LocalPath))
         End Function
         End Function
         For i = 0 To BasicArray.Count - 1
         For i = 0 To BasicArray.Count - 1
-            Dim Key As String = BasicArray(i).Name & BasicArray(i).IsNatives.ToString & BasicArray(i).IsJumpLoader.ToString
+            Dim Key As String = BasicArray(i).Name & BasicArray(i).IsNatives.ToString
             If ResultArray.ContainsKey(Key) Then
             If ResultArray.ContainsKey(Key) Then
                 Dim BasicArrayVersion As String = GetVersion(BasicArray(i))
                 Dim BasicArrayVersion As String = GetVersion(BasicArray(i))
                 Dim ResultArrayVersion As String = GetVersion(ResultArray(Key))
                 Dim ResultArrayVersion As String = GetVersion(ResultArray(Key))
@@ -1964,7 +1943,7 @@ OnLoaded:
     ''' 获取版本缺失的支持库文件所对应的 NetTaskFile。
     ''' 获取版本缺失的支持库文件所对应的 NetTaskFile。
     ''' </summary>
     ''' </summary>
     Public Function McLibFix(Version As McVersion) As List(Of NetFile)
     Public Function McLibFix(Version As McVersion) As List(Of NetFile)
-        If Not Version.IsLoaded Then Version.Load() '确保例如 JumpLoader 等项被合并入 Json
+        If Not Version.IsLoaded Then Version.Load()
         Dim Result As New List(Of NetFile)
         Dim Result As New List(Of NetFile)
 
 
         '更新此方法时需要同步更新 Forge 新版自动安装方法!
         '更新此方法时需要同步更新 Forge 新版自动安装方法!
@@ -1978,7 +1957,7 @@ OnLoaded:
         End Try
         End Try
 
 
         'Library 文件
         'Library 文件
-        Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False), JumpLoaderFolder:=Version.PathIndie & ".jumploader\"))
+        Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False)))
 
 
         '统一通行证文件
         '统一通行证文件
         If Setup.Get("VersionServerLogin", Version:=Version) = 3 Then
         If Setup.Get("VersionServerLogin", Version:=Version) = 3 Then
@@ -1988,8 +1967,7 @@ OnLoaded:
             Try
             Try
                 Log("[Minecraft] 开始获取统一通行证下载信息")
                 Log("[Minecraft] 开始获取统一通行证下载信息")
                 '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/
                 '测试链接: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), RequireJson:=True))
             Catch ex As Exception
             Catch ex As Exception
                 Log(ex, "获取统一通行证下载信息失败")
                 Log(ex, "获取统一通行证下载信息失败")
             End Try
             End Try
@@ -2006,17 +1984,20 @@ OnLoaded:
 
 
         'Authlib-Injector 文件
         'Authlib-Injector 文件
         If Setup.Get("VersionServerLogin", Version:=Version) = 4 Then
         If Setup.Get("VersionServerLogin", Version:=Version) = 4 Then
-            Dim TargetFile = PathPure & "\authlib-injector.jar"
+            Dim TargetFile = PathPure & "authlib-injector.jar"
             Dim DownloadInfo As JObject = Nothing
             Dim DownloadInfo As JObject = Nothing
             '获取下载信息
             '获取下载信息
             Try
             Try
                 Log("[Minecraft] 开始获取 Authlib-Injector 下载信息")
                 Log("[Minecraft] 开始获取 Authlib-Injector 下载信息")
-                DownloadInfo = GetJson(NetGetCodeByLoader({
+                DownloadInfo = GetJson(NetRequestByClientRetry(
                         "https://authlib-injector.yushi.moe/artifact/latest.json",
                         "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", RequireJson:=True))
             Catch ex As Exception
             Catch ex As Exception
-                Log(ex, "获取 Authlib-Injector 下载信息失败")
+                If File.Exists(TargetFile) Then
+                    Log(ex, "获取 Authlib-Injector 下载信息失败")
+                Else
+                    Throw New Exception("获取 Authlib-Injector 下载信息失败", ex)
+                End If
             End Try
             End Try
             '校验文件
             '校验文件
             If DownloadInfo IsNot Nothing Then
             If DownloadInfo IsNot Nothing Then
@@ -2053,30 +2034,39 @@ OnLoaded:
     ''' <summary>
     ''' <summary>
     ''' 将 McLibToken 列表转换为 NetFile。无需下载的文件会被自动过滤。
     ''' 将 McLibToken 列表转换为 NetFile。无需下载的文件会被自动过滤。
     ''' </summary>
     ''' </summary>
-    Public Function McLibFixFromLibToken(Libs As List(Of McLibToken), Optional CustomMcFolder As String = Nothing, Optional JumpLoaderFolder As String = Nothing) As List(Of NetFile)
+    Public Function McLibFixFromLibToken(Libs As List(Of McLibToken), Optional CustomMcFolder As String = Nothing) As List(Of NetFile)
         CustomMcFolder = If(CustomMcFolder, PathMcFolder)
         CustomMcFolder = If(CustomMcFolder, PathMcFolder)
         Dim Result As New List(Of NetFile)
         Dim Result As New List(Of NetFile)
         '获取
         '获取
         For Each Token As McLibToken In Libs
         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
             If Checker.Check(Token.LocalPath) Is Nothing Then Continue For
             '文件不符合,添加下载
             '文件不符合,添加下载
             Dim Urls As New List(Of String)
             Dim Urls As New List(Of String)
-            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(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries", "").Replace("\", "/")
+            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
             End If
             If Token.Url IsNot Nothing Then
             If Token.Url IsNot Nothing Then
                 '获取 URL 的真实地址
                 '获取 URL 的真实地址
                 Urls.Add(Token.Url)
                 Urls.Add(Token.Url)
                 If Token.Url.Contains("launcher.mojang.com/v1/objects") OrElse Token.Url.Contains("client.txt") OrElse
                 If Token.Url.Contains("launcher.mojang.com/v1/objects") OrElse Token.Url.Contains("client.txt") OrElse
                    Token.Url.Contains(".tsrg") Then
                    Token.Url.Contains(".tsrg") Then
-                    Urls.AddRange(DlSourceLauncherOrMetaGet(Token.Url).ToList()) 'Mappings(#4425)
+                    Urls.AddRange(DlSourceLauncherOrMetaGet(Token.Url)) 'Mappings(#4425)
                 End If
                 End If
                 If Token.Url.Contains("maven") Then
                 If Token.Url.Contains("maven") Then
-                    Urls.Insert(0, Token.Url.Replace(Mid(Token.Url, 1, Token.Url.IndexOfF("maven")), "https://bmclapi2.bangbang93.com/").
-                        Replace("maven.fabricmc.net", "maven").Replace("maven.minecraftforge.net", "maven").Replace("maven.neoforged.net/releases", "maven"))
+                    Dim BmclapiUrl As String =
+                        Token.Url.Replace(Mid(Token.Url, 1, Token.Url.IndexOfF("maven")), "https://bmclapi2.bangbang93.com/").Replace("maven.fabricmc.net", "maven").Replace("maven.minecraftforge.net", "maven").Replace("maven.neoforged.net/releases", "maven")
+                    If DlSourcePreferMojang Then
+                        Urls.Add(BmclapiUrl) '官方源优先
+                    Else
+                        Urls.Insert(0, BmclapiUrl) '镜像源优先
+                    End If
                 End If
                 End If
             End If
             End If
             If Token.LocalPath.Contains("transformer-discovery-service") Then
             If Token.LocalPath.Contains("transformer-discovery-service") Then
@@ -2086,13 +2076,13 @@ OnLoaded:
                 Continue For
                 Continue For
             ElseIf Token.LocalPath.Contains("optifine\OptiFine") Then
             ElseIf Token.LocalPath.Contains("optifine\OptiFine") Then
                 'OptiFine 主 Jar
                 'OptiFine 主 Jar
-                Dim OptiFineBase As String = Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries\optifine\OptiFine\", "").Split("_")(0) & "/" & GetFileNameFromPath(Token.LocalPath).Replace("-", "_")
+                Dim OptiFineBase As String = Token.LocalPath.Replace(CustomMcFolder & "libraries\optifine\OptiFine\", "").Split("_")(0) & "/" & GetFileNameFromPath(Token.LocalPath).Replace("-", "_")
                 OptiFineBase = "/maven/com/optifine/" & OptiFineBase
                 OptiFineBase = "/maven/com/optifine/" & OptiFineBase
                 If OptiFineBase.Contains("_pre") Then OptiFineBase = OptiFineBase.Replace("com/optifine/", "com/optifine/preview_")
                 If OptiFineBase.Contains("_pre") Then OptiFineBase = OptiFineBase.Replace("com/optifine/", "com/optifine/preview_")
                 Urls.Add("https://bmclapi2.bangbang93.com" & OptiFineBase)
                 Urls.Add("https://bmclapi2.bangbang93.com" & OptiFineBase)
             ElseIf Urls.Count <= 2 Then
             ElseIf Urls.Count <= 2 Then
                 '普通文件
                 '普通文件
-                Urls.AddRange(DlSourceLibraryGet("https://libraries.minecraft.net" & Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries", "").Replace("\", "/")))
+                Urls.AddRange(DlSourceLibraryGet("https://libraries.minecraft.net" & Token.LocalPath.Replace(CustomMcFolder & "libraries", "").Replace("\", "/")))
             End If
             End If
             Result.Add(New NetFile(Urls.Distinct, Token.LocalPath, Checker))
             Result.Add(New NetFile(Urls.Distinct, Token.LocalPath, Checker))
         Next
         Next
@@ -2231,16 +2221,16 @@ OnLoaded:
                 Dim LocalPath As String
                 Dim LocalPath As String
                 If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToObject(Of Boolean) Then
                 If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToObject(Of Boolean) Then
                     'Remap
                     '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
                 ElseIf Json("virtual") IsNot Nothing AndAlso Json("virtual").ToObject(Of Boolean) Then
                     'Virtual
                     'Virtual
-                    LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\")
+                    LocalPath = $"{PathMcFolder}assets\virtual\legacy\{File.Name}"
                 Else
                 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
                 End If
                 Result.Add(New McAssetsToken With {
                 Result.Add(New McAssetsToken With {
-                    .LocalPath = LocalPath,
+                    .LocalPath = LocalPath.Replace("/", "\"),
                     .SourcePath = File.Name,
                     .SourcePath = File.Name,
                     .Hash = File.Value("hash").ToString,
                     .Hash = File.Value("hash").ToString,
                     .Size = File.Value("size").ToString
                     .Size = File.Value("size").ToString
@@ -2275,7 +2265,7 @@ OnLoaded:
                 If File.Exists AndAlso (Token.Size = 0 OrElse Token.Size = File.Length) AndAlso
                 If File.Exists AndAlso (Token.Size = 0 OrElse Token.Size = File.Length) AndAlso
                     (Not CheckHash OrElse Token.Hash Is Nothing OrElse Token.Hash = GetFileSHA1(Token.LocalPath)) Then Continue For
                     (Not CheckHash OrElse Token.Hash Is Nothing OrElse Token.Hash = GetFileSHA1(Token.LocalPath)) Then Continue For
                 '文件不存在,添加下载
                 '文件不存在,添加下载
-                Result.Add(New NetFile(DlSourceResourceGet("https://resources.download.minecraft.net/" & Left(Token.Hash, 2) & "/" & Token.Hash), Token.LocalPath, New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.Hash)))
+                Result.Add(New NetFile(DlSourceAssetsGet($"https://resources.download.minecraft.net/{Left(Token.Hash, 2)}/{Token.Hash}"), Token.LocalPath, New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.Hash)))
             Next
             Next
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "获取版本缺失的资源文件下载列表失败")
             Log(ex, "获取版本缺失的资源文件下载列表失败")
@@ -2302,7 +2292,7 @@ OnLoaded:
                 End If
                 End If
             Next
             Next
             '进行提示
             '进行提示
-            If Version Is Nothing Then Exit Sub
+            If Version Is Nothing Then Return
             Dim Time As Date = Version("releaseTime")
             Dim Time As Date = Version("releaseTime")
             Dim MsgBoxText As String = $"新版本:{VersionName}{vbCrLf}" &
             Dim MsgBoxText As String = $"新版本:{VersionName}{vbCrLf}" &
                 If((Date.Now - Time).TotalDays > 1, "更新时间:" & Time.ToString, "更新于:" & GetTimeSpanString(Time - Date.Now, False))
                 If((Date.Now - Time).TotalDays > 1, "更新时间:" & Time.ToString, "更新于:" & GetTimeSpanString(Time - Date.Now, False))
@@ -2400,26 +2390,45 @@ NextEntry:
     End Class
     End Class
 
 
     ''' <summary>
     ''' <summary>
-    ''' 为邮箱地址或手机号账号进行部分打码。
+    ''' 判断版本名是否类似正式版。
     ''' </summary>
     ''' </summary>
-    Public Function AccountFilter(Account As String) As String
-        If Account.Contains("@") Then
-            '是邮箱
-            Dim Splits = Account.Split("@")
-            'If Splits(0).Count >= 6 Then
-            '    '前半部分至少 6 位,屏蔽后 4 位
-            '    Return Mid(Splits(0), 1, Splits(0).Count - 4) & "****" & "@" & Splits(1)
-            'Else
-            '前半部分不到 6 位,返回全 *
-            Return "".PadLeft(Splits(0).Count, "*") & "@" & Splits(1)
-            'End If
-        ElseIf Account.Count >= 6 Then
-            '至少 6 位,屏蔽后 4 位
-            Return Mid(Account, 1, Account.Count - 4) & "****"
-        Else
-            '不到 6 位,返回全 *
-            Return "".PadLeft(Account.Count, "*")
+    Public Function IsVersionNameLikeRelease(VerName As String) As Boolean
+        Return VerName.StartsWithF("1.") AndAlso Not (VerName.Contains("w") OrElse VerName.Contains("pre") OrElse VerName.Contains("rc") OrElse VerName.Contains("-"))
+    End Function
+
+    ''' <summary>
+    ''' 打码字符串中的 AccessToken。
+    ''' </summary>
+    Public Function FilterAccessToken(Raw As String, FilterChar As Char) As String
+        '打码 "accessToken " 后的内容
+        If Raw.Contains("accessToken ") Then
+            For Each Token In RegexSearch(Raw, "(?<=accessToken ([^ ]{5}))[^ ]+(?=[^ ]{5})")
+                Raw = Raw.Replace(Token, New String(FilterChar, Token.Count))
+            Next
+        End If
+        '打码当前登录的结果
+        Dim AccessToken As String = McLoginLoader.Output.AccessToken
+        If AccessToken IsNot Nothing AndAlso AccessToken.Length >= 10 AndAlso Raw.ContainsF(AccessToken, True) AndAlso
+            McLoginLoader.Output.Uuid <> McLoginLoader.Output.AccessToken Then 'UUID 和 AccessToken 一样则不打码
+            Raw = Raw.Replace(AccessToken, Left(AccessToken, 5) & New String(FilterChar, AccessToken.Length - 10) & Right(AccessToken, 5))
+        End If
+        Return Raw
+    End Function
+    ''' <summary>
+    ''' 打码字符串中的 Windows 用户名。
+    ''' </summary>
+    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("\Users\" & Token, "\Users\" & New String(FilterChar, Token.Count))
+            Next
+        End If
+        If Raw.Contains(":/Users/") Then
+            For Each Token In RegexSearch(Raw, "(?<=:/Users/)[^/]+")
+                Raw = Raw.Replace("/Users/" & Token, "/Users/" & New String(FilterChar, Token.Count))
+            Next
         End If
         End If
+        Return Raw
     End Function
     End Function
 
 
 End Module
 End Module

+ 46 - 42
Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb

@@ -1,7 +1,7 @@
 Imports System.IO.Compression
 Imports System.IO.Compression
 
 
 Public Module ModMod
 Public Module ModMod
-    Private Const LocalModCacheVersion As Integer = 7
+    Private Const LocalModCacheVersion As Integer = 8
 
 
     Public Class McMod
     Public Class McMod
 
 
@@ -73,7 +73,7 @@ Public Module ModMod
             Get
             Get
                 If _Name Is Nothing Then Load()
                 If _Name Is Nothing Then Load()
                 If _Name Is Nothing Then _Name = _ModId
                 If _Name Is Nothing Then _Name = _ModId
-                If _Name Is Nothing Then _Name = GetFileNameWithoutExtentionFromPath(Path)
+                If _Name Is Nothing Then _Name = IO.Path.GetFileNameWithoutExtension(Path)
                 Return _Name
                 Return _Name
             End Get
             End Get
             Set(value As String)
             Set(value As String)
@@ -188,9 +188,9 @@ Public Module ModMod
         Private _Dependencies As New Dictionary(Of String, String)
         Private _Dependencies As New Dictionary(Of String, String)
         Private Sub AddDependency(ModID As String, Optional VersionRequirement As String = Nothing)
         Private Sub AddDependency(ModID As String, Optional VersionRequirement As String = Nothing)
             '确保信息正确
             '确保信息正确
-            If ModID Is Nothing OrElse ModID.Count < 2 Then Exit Sub
+            If ModID Is Nothing OrElse ModID.Count < 2 Then Return
             ModID = ModID.ToLower
             ModID = ModID.ToLower
-            If ModID = "name" OrElse Val(ModID).ToString = ModID Then Exit Sub '跳过 name 与纯数字 id
+            If ModID = "name" OrElse Val(ModID).ToString = ModID Then Return '跳过 name 与纯数字 id
             If VersionRequirement Is Nothing OrElse ((Not VersionRequirement.Contains(".")) AndAlso (Not VersionRequirement.Contains("-"))) OrElse VersionRequirement.Contains("$") Then
             If VersionRequirement Is Nothing OrElse ((Not VersionRequirement.Contains(".")) AndAlso (Not VersionRequirement.Contains("-"))) OrElse VersionRequirement.Contains("$") Then
                 VersionRequirement = Nothing
                 VersionRequirement = Nothing
             Else
             Else
@@ -279,7 +279,7 @@ Public Module ModMod
         ''' 进行文件可用性检查与 .class 以外的信息获取。
         ''' 进行文件可用性检查与 .class 以外的信息获取。
         ''' </summary>
         ''' </summary>
         Public Sub Load(Optional ForceReload As Boolean = False)
         Public Sub Load(Optional ForceReload As Boolean = False)
-            If IsLoaded AndAlso Not ForceReload Then Exit Sub
+            If IsLoaded AndAlso Not ForceReload Then Return
             '初始化
             '初始化
             Init()
             Init()
             Dim Jar As ZipArchive = Nothing
             Dim Jar As ZipArchive = Nothing
@@ -688,7 +688,7 @@ Finished:
         ''' </summary>
         ''' </summary>
         Public ReadOnly Property CanUpdate As Boolean
         Public ReadOnly Property CanUpdate As Boolean
             Get
             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 Get
         End Property
         End Property
 
 
@@ -819,7 +819,7 @@ Finished:
                 Try
                 Try
                     RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在更新 Mod")
                     RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在更新 Mod")
                     Do Until Not PageVersionMod.UpdatingVersions.Contains(Loader.Input)
                     Do Until Not PageVersionMod.UpdatingVersions.Contains(Loader.Input)
-                        If Loader.IsAborted Then Exit Sub
+                        If Loader.IsAborted Then Return
                         Thread.Sleep(100)
                         Thread.Sleep(100)
                     Loop
                     Loop
                 Finally
                 Finally
@@ -837,7 +837,7 @@ Finished:
                         '仅当 Forge 1.13- 且文件夹名与版本号相同时,才加载该子文件夹下的 Mod
                         '仅当 Forge 1.13- 且文件夹名与版本号相同时,才加载该子文件夹下的 Mod
                         If Not (PageVersionLeft.Version IsNot Nothing AndAlso PageVersionLeft.Version.Version.HasForge AndAlso
                         If Not (PageVersionLeft.Version IsNot Nothing AndAlso PageVersionLeft.Version.Version.HasForge AndAlso
                                 PageVersionLeft.Version.Version.McCodeMain < 13 AndAlso
                                 PageVersionLeft.Version.Version.McCodeMain < 13 AndAlso
-                                File.Directory.Name = "1." & PageVersionLeft.Version.Version.McCodeMain & "." & PageVersionLeft.Version.Version.McCodeSub) Then
+                                File.Directory.Name = $"1.{PageVersionLeft.Version.Version.McCodeMain}.{PageVersionLeft.Version.Version.McCodeSub}") Then
                             Continue For
                             Continue For
                         End If
                         End If
                     End If
                     End If
@@ -872,7 +872,7 @@ Finished:
             Dim ModUpdateList As New List(Of McMod)
             Dim ModUpdateList As New List(Of McMod)
             For Each ModFile As FileInfo In ModFileList
             For Each ModFile As FileInfo In ModFileList
                 Loader.Progress += 0.94 / ModFileList.Count
                 Loader.Progress += 0.94 / ModFileList.Count
-                If Loader.IsAborted Then Exit Sub
+                If Loader.IsAborted Then Return
                 '加载 McMod 对象
                 '加载 McMod 对象
                 Dim ModEntry As New McMod(ModFile.FullName)
                 Dim ModEntry As New McMod(ModFile.FullName)
                 ModEntry.Load()
                 ModEntry.Load()
@@ -912,7 +912,7 @@ Finished:
             End Function)
             End Function)
 
 
             '回设
             '回设
-            If Loader.IsAborted Then Exit Sub
+            If Loader.IsAborted Then Return
             Loader.Output = ModList
             Loader.Output = ModList
 
 
             '开始联网加载
             '开始联网加载
@@ -939,18 +939,18 @@ Finished:
         '开始网络获取
         '开始网络获取
         Log($"[Mod] 目标加载器:{ModLoaders.Join("/")},版本:{McVersion}")
         Log($"[Mod] 目标加载器:{ModLoaders.Join("/")},版本:{McVersion}")
         Dim EndedThreadCount As Integer = 0, IsFailed As Boolean = False
         Dim EndedThreadCount As Integer = 0, IsFailed As Boolean = False
-        Dim MainThread As Thread = Thread.CurrentThread
+        Dim CurrentTaskThread As Thread = Thread.CurrentThread
         '从 Modrinth 获取信息
         '从 Modrinth 获取信息
         RunInNewThread(
         RunInNewThread(
         Sub()
         Sub()
             Try
             Try
                 '步骤 1:获取 Hash 与对应的工程 ID
                 '步骤 1:获取 Hash 与对应的工程 ID
                 Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList()
                 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 的对应信息")
                 Log($"[Mod] 从 Modrinth 获取到 {ModrinthVersion.Count} 个本地 Mod 的对应信息")
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
-                If ModrinthVersion.Count = 0 Then Exit Sub
+                If ModrinthVersion.Count = 0 Then Return
                 Dim ModrinthMapping As New Dictionary(Of String, List(Of McMod))
                 Dim ModrinthMapping As New Dictionary(Of String, List(Of McMod))
                 For Each Entry In Mods
                 For Each Entry In Mods
                     If Not ModrinthVersion.ContainsKey(Entry.ModrinthHash) Then Continue For
                     If Not ModrinthVersion.ContainsKey(Entry.ModrinthHash) Then Continue For
@@ -963,13 +963,12 @@ Finished:
                     Dim File As New CompFile(ModrinthVersion(Entry.ModrinthHash), CompType.Mod)
                     Dim File As New CompFile(ModrinthVersion(Entry.ModrinthHash), CompType.Mod)
                     If Entry.CompFile Is Nothing OrElse Entry.CompFile.ReleaseDate < File.ReleaseDate Then Entry.CompFile = File
                     If Entry.CompFile Is Nothing OrElse Entry.CompFile.ReleaseDate < File.ReleaseDate Then Entry.CompFile = File
                 Next
                 Next
-                If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
+                If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return
                 Log($"[Mod] 需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息")
                 Log($"[Mod] 需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息")
                 '步骤 3:获取工程信息
                 '步骤 3:获取工程信息
-                If Not ModrinthMapping.Any() Then Exit Sub
-                Dim ModrinthProject = CType(GetJson(DlModRequest(
-                    $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]",
-                    "GET", "", "application/json")), JArray)
+                If Not ModrinthMapping.Any() Then Return
+                Dim ModrinthProject As JArray = DlModRequest(
+                    $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]")
                 For Each ProjectJson In ModrinthProject
                 For Each ProjectJson In ModrinthProject
                     Dim Project As New CompProject(ProjectJson)
                     Dim Project As New CompProject(ProjectJson)
                     For Each Entry In ModrinthMapping(Project.Id)
                     For Each Entry In ModrinthMapping(Project.Id)
@@ -978,9 +977,9 @@ Finished:
                 Next
                 Next
                 Log($"[Mod] 已从 Modrinth 获取本地 Mod 信息,继续获取更新信息")
                 Log($"[Mod] 已从 Modrinth 获取本地 Mod 信息,继续获取更新信息")
                 '步骤 4:获取更新信息
                 '步骤 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"", 
                     $"{{""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
                 For Each Entry In Mods
                     If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.CompFile Is Nothing Then Continue For
                     If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.CompFile Is Nothing Then Continue For
                     Dim UpdateFile As New CompFile(ModrinthUpdate(Entry.ModrinthHash), CompType.Mod)
                     Dim UpdateFile As New CompFile(ModrinthUpdate(Entry.ModrinthHash), CompType.Mod)
@@ -1013,13 +1012,13 @@ Finished:
                 Dim CurseForgeHashes As New List(Of UInteger)
                 Dim CurseForgeHashes As New List(Of UInteger)
                 For Each Entry In Mods
                 For Each Entry In Mods
                     CurseForgeHashes.Add(Entry.CurseForgeHash)
                     CurseForgeHashes.Add(Entry.CurseForgeHash)
-                    If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
+                    If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return
                 Next
                 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 的对应信息")
                 Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息")
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
                 '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
-                If Not CurseForgeRaw.Any() Then Exit Sub
+                If Not CurseForgeRaw.Any() Then Return
                 Dim CurseForgeMapping As New Dictionary(Of Integer, List(Of McMod))
                 Dim CurseForgeMapping As New Dictionary(Of Integer, List(Of McMod))
                 For Each Project In CurseForgeRaw
                 For Each Project In CurseForgeRaw
                     Dim ProjectId = Project("id").ToString
                     Dim ProjectId = Project("id").ToString
@@ -1034,12 +1033,12 @@ Finished:
                         If Entry.CompFile Is Nothing OrElse Entry.CompFile.ReleaseDate < File.ReleaseDate Then Entry.CompFile = File
                         If Entry.CompFile Is Nothing OrElse Entry.CompFile.ReleaseDate < File.ReleaseDate Then Entry.CompFile = File
                     Next
                     Next
                 Next
                 Next
-                If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
+                If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return
                 Log($"[Mod] 需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息")
                 Log($"[Mod] 需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息")
                 '步骤 3:获取工程信息
                 '步骤 3:获取工程信息
-                If Not CurseForgeMapping.Any() Then Exit Sub
-                Dim CurseForgeProject = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods", "POST",
-                    $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data")
+                If Not CurseForgeMapping.Any() Then Return
+                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 UpdateFileIds As New Dictionary(Of Integer, List(Of McMod)) 'FileId -> 本地 Mod 文件列表
                 Dim FileIdToProjectSlug As New Dictionary(Of Integer, String)
                 Dim FileIdToProjectSlug As New Dictionary(Of Integer, String)
                 For Each ProjectJson In CurseForgeProject
                 For Each ProjectJson In CurseForgeProject
@@ -1078,9 +1077,9 @@ Finished:
                 Next
                 Next
                 Log($"[Mod] 已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息")
                 Log($"[Mod] 已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息")
                 '步骤 4:获取更新文件信息
                 '步骤 4:获取更新文件信息
-                If Not UpdateFileIds.Any() Then Exit Sub
-                Dim CurseForgeFiles = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST",
-                                    $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")), JObject)("data")
+                If Not UpdateFileIds.Any() Then Return
+                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)
                 Dim UpdateFiles As New Dictionary(Of McMod, CompFile)
                 For Each FileJson In CurseForgeFiles
                 For Each FileJson In CurseForgeFiles
                     Dim File As New CompFile(FileJson, CompType.Mod)
                     Dim File As New CompFile(FileJson, CompType.Mod)
@@ -1114,20 +1113,25 @@ Finished:
         End Sub, "Mod List Detail Loader CurseForge")
         End Sub, "Mod List Detail Loader CurseForge")
         '等待线程结束
         '等待线程结束
         Do Until EndedThreadCount = 2
         Do Until EndedThreadCount = 2
-            If Loader.IsAborted Then Exit Sub
             Thread.Sleep(10)
             Thread.Sleep(10)
+            If Loader.IsAborted Then Return
         Loop
         Loop
         '保存缓存
         '保存缓存
         Mods = Mods.Where(Function(m) m.Comp IsNot Nothing).ToList()
         Mods = Mods.Where(Function(m) m.Comp IsNot Nothing).ToList()
         Log($"[Mod] 联网获取本地 Mod 信息完成,为 {Mods.Count} 个 Mod 更新缓存")
         Log($"[Mod] 联网获取本地 Mod 信息完成,为 {Mods.Count} 个 Mod 更新缓存")
-        If Not Mods.Any() Then Exit Sub
+        If Not Mods.Any() Then Return
         For Each Entry In Mods
         For Each Entry In Mods
             Entry.CompLoaded = Not IsFailed
             Entry.CompLoaded = Not IsFailed
             Cache(Entry.ModrinthHash & McVersion & ModLoaders.Join("")) = Entry.ToJson()
             Cache(Entry.ModrinthHash & McVersion & ModLoaders.Join("")) = Entry.ToJson()
         Next
         Next
         WriteFile(PathTemp & "Cache\LocalMod.json", Cache.ToString(If(ModeDebug, Newtonsoft.Json.Formatting.Indented, Newtonsoft.Json.Formatting.None)))
         WriteFile(PathTemp & "Cache\LocalMod.json", Cache.ToString(If(ModeDebug, Newtonsoft.Json.Formatting.Indented, Newtonsoft.Json.Formatting.None)))
         '刷新边栏
         '刷新边栏
-        RunInUi(Sub() FrmVersionMod?.RefreshBars())
+        If Loader.IsAborted Then Return
+        If FrmVersionMod?.Filter = PageVersionMod.FilterType.CanUpdate Then
+            RunInUi(Sub() FrmVersionMod?.RefreshUI()) '同步 “可更新” 列表 (#4677)
+        Else
+            RunInUi(Sub() FrmVersionMod?.RefreshBars())
+        End If
     End Sub
     End Sub
     Public Function GetTargetModLoaders() As List(Of CompModLoaderType)
     Public Function GetTargetModLoaders() As List(Of CompModLoaderType)
         Dim ModLoaders As New List(Of CompModLoaderType)
         Dim ModLoaders As New List(Of CompModLoaderType)
@@ -1209,7 +1213,7 @@ Finished:
                         If ReqVersionHead = ReqVersionTail Then
                         If ReqVersionHead = ReqVersionTail Then
                             VersionRequire = "应为 " & ReqVersionHead
                             VersionRequire = "应为 " & ReqVersionHead
                         ElseIf ReqVersionHead.Contains(".") AndAlso ReqVersionTail.Contains(".") Then
                         ElseIf ReqVersionHead.Contains(".") AndAlso ReqVersionTail.Contains(".") Then
-                            VersionRequire = "应为 " & ReqVersionHead & " 至 " & ReqVersionTail
+                            VersionRequire = $"应为 {ReqVersionHead} 至 {ReqVersionTail}"
                         ElseIf ReqVersionHead.Contains(".") Then
                         ElseIf ReqVersionHead.Contains(".") Then
                             If ReqVersionHeadCanEqual Then
                             If ReqVersionHeadCanEqual Then
                                 VersionRequire = "最低应为 " & ReqVersionHead
                                 VersionRequire = "最低应为 " & ReqVersionHead
@@ -1227,7 +1231,7 @@ Finished:
                         End If
                         End If
                         '检查前置 Mod 是否存在,并获取其版本
                         '检查前置 Mod 是否存在,并获取其版本
                         If Not CurrentDependencies.ContainsKey(ReqId) Then
                         If Not CurrentDependencies.ContainsKey(ReqId) Then
-                            Result.Add("缺少前置 Mod:" & ReqId & If(VersionRequire = "", "", ",其版本" & VersionRequire) & "。" & vbCrLf & " - " & ModEntity.FileName)
+                            Result.Add($"缺少前置 Mod:{ReqId}{If(VersionRequire = "", "", ",其版本" & VersionRequire)}。{vbCrLf} - {ModEntity.FileName}")
                             Continue For
                             Continue For
                         End If
                         End If
                         Dim CurrentVersion As String = If(CurrentDependencies(ReqId)(0), "0.0")
                         Dim CurrentVersion As String = If(CurrentDependencies(ReqId)(0), "0.0")
@@ -1236,7 +1240,7 @@ Finished:
                         If ReqVersionHead.Contains(".") Then
                         If ReqVersionHead.Contains(".") Then
                             If VersionSortInteger(ReqVersionHead, CurrentVersion) > If(ReqVersionHeadCanEqual, 0, -1) Then
                             If VersionSortInteger(ReqVersionHead, CurrentVersion) > If(ReqVersionHeadCanEqual, 0, -1) Then
                                 Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过低,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
                                 Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过低,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
-                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), ""))
+                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", $"{vbCrLf} - 前置:{CurrentDependencies(ReqId)(1)}", ""))
                                 Continue For
                                 Continue For
                             End If
                             End If
                         End If
                         End If
@@ -1244,26 +1248,26 @@ Finished:
                         If ReqVersionTail.Contains(".") Then
                         If ReqVersionTail.Contains(".") Then
                             If VersionSortInteger(CurrentVersion, ReqVersionTail) > If(ReqVersionTailCanEqual, 0, -1) Then
                             If VersionSortInteger(CurrentVersion, ReqVersionTail) > If(ReqVersionTailCanEqual, 0, -1) Then
                                 Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过高,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
                                 Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过高,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf &
-                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), ""))
+                                           " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", $"{vbCrLf} - 前置:{CurrentDependencies(ReqId)(1)}", ""))
                                 Continue For
                                 Continue For
                             End If
                             End If
                         End If
                         End If
                     Else
                     Else
                         If Not CurrentDependencies.ContainsKey(Dependency.Key) Then
                         If Not CurrentDependencies.ContainsKey(Dependency.Key) Then
-                            Result.Add("缺少前置 Mod:" & Dependency.Key & "。" & vbCrLf & " - " & ModEntity.FileName)
+                            Result.Add($"缺少前置 Mod:{Dependency.Key}。{vbCrLf} - {ModEntity.FileName}")
                             Continue For
                             Continue For
                         End If
                         End If
                     End If
                     End If
                 Next
                 Next
             Catch ex As Exception
             Catch ex As Exception
-                Result.Add("检查 Mod 时出错:" & GetExceptionSummary(ex) & vbCrLf & " - " & ModEntity.FileName)
+                Result.Add($"检查 Mod 时出错:{ex.GetBrief()}{vbCrLf} - {ModEntity.FileName}")
                 Log(ex, "检查 Mod 时出错")
                 Log(ex, "检查 Mod 时出错")
             End Try
             End Try
         Next
         Next
         If Not Result.Any() Then
         If Not Result.Any() Then
             Log("[Minecraft] Mod 检查未发现异常")
             Log("[Minecraft] Mod 检查未发现异常")
         Else
         Else
-            Log("[Minecraft] Mod 检查异常结果:" & vbCrLf & Join(Result, vbCrLf))
+            Log($"[Minecraft] Mod 检查异常结果:{vbCrLf}{Join(Result, vbCrLf)}")
         End If
         End If
         Return Result
         Return Result
     End Function
     End Function

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

@@ -8,7 +8,7 @@ Public Module ModModpack
     ''' </summary>
     ''' </summary>
     Public Sub ModpackInstall()
     Public Sub ModpackInstall()
         Dim File As String = SelectFile("整合包文件(*.rar;*.zip;*.mrpack)|*.rar;*.zip;*.mrpack", "选择整合包压缩文件") '选择整合包文件
         Dim File As String = SelectFile("整合包文件(*.rar;*.zip;*.mrpack)|*.rar;*.zip;*.mrpack", "选择整合包压缩文件") '选择整合包文件
-        If String.IsNullOrEmpty(File) Then Exit Sub
+        If String.IsNullOrEmpty(File) Then Return
         RunInThread(
         RunInThread(
         Sub()
         Sub()
             Try
             Try
@@ -72,7 +72,7 @@ Public Module ModModpack
                     If FullNames(1) = "modpack.zip" OrElse FullNames(1) = "modpack.mrpack" Then PackType = 9 : Exit Try '带启动器的压缩包
                     If FullNames(1) = "modpack.zip" OrElse FullNames(1) = "modpack.mrpack" Then PackType = 9 : Exit Try '带启动器的压缩包
                 Next
                 Next
             Catch ex As Exception
             Catch ex As Exception
-                If GetExceptionDetail(ex, True).Contains("Error.WinIOError") Then
+                If ex.GetDetail(True).Contains("Error.WinIOError") Then
                     Throw New Exception("打开整合包文件失败", ex)
                     Throw New Exception("打开整合包文件失败", ex)
                 ElseIf File.EndsWithF(".rar", True) Then
                 ElseIf File.EndsWithF(".rar", True) Then
                     Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex)
                     Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex)
@@ -109,30 +109,71 @@ Public Module ModModpack
         End Try
         End Try
     End Function
     End Function
 
 
-    Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase, LoaderProgressDelta As Double)
+    Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase, ProgressIncrement As Double)
         '解压文件
         '解压文件
         Dim RetryCount As Integer = 1
         Dim RetryCount As Integer = 1
         Dim Encode = Encoding.GetEncoding("GB18030")
         Dim Encode = Encoding.GetEncoding("GB18030")
+        Dim InitialProgress = Loader.Progress
         Try
         Try
 Retry:
 Retry:
-            '完全不知道为啥会出现文件正在被另一进程使用的问题,总之多试试
+            Loader.Progress = InitialProgress
             DeleteDirectory(InstallTemp)
             DeleteDirectory(InstallTemp)
-            ExtractFile(FileAddress, InstallTemp, Encode, ProgressIncrementHandler:=Sub(Delta) Loader.Progress += Delta * LoaderProgressDelta)
+            ExtractFile(FileAddress, InstallTemp, Encode, ProgressIncrementHandler:=Sub(Delta) Loader.Progress += Delta * ProgressIncrement)
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "第 " & RetryCount & " 次解压尝试失败")
             Log(ex, "第 " & RetryCount & " 次解压尝试失败")
             If TypeOf ex Is ArgumentException Then
             If TypeOf ex Is ArgumentException Then
                 Encode = Encoding.UTF8
                 Encode = Encoding.UTF8
                 Log("[ModPack] 已切换压缩包解压编码为 UTF8")
                 Log("[ModPack] 已切换压缩包解压编码为 UTF8")
             End If
             End If
+            '完全不知道为啥会出现文件正在被另一进程使用的问题,总之加个重试
             If RetryCount < 5 Then
             If RetryCount < 5 Then
                 Thread.Sleep(RetryCount * 2000)
                 Thread.Sleep(RetryCount * 2000)
-                If Loader IsNot Nothing AndAlso Loader.LoadingState <> MyLoading.MyLoadingState.Run Then Exit Sub
+                If Loader IsNot Nothing AndAlso Loader.LoadingState <> MyLoading.MyLoadingState.Run Then Return
                 RetryCount += 1
                 RetryCount += 1
                 GoTo Retry
                 GoTo Retry
             Else
             Else
                 Throw New Exception("解压整合包文件失败", ex)
                 Throw New Exception("解压整合包文件失败", ex)
             End If
             End If
         End Try
         End Try
+        Loader.Progress = InitialProgress + ProgressIncrement
+    End Sub
+    ''' <summary>
+    ''' 从整合包的 override 目录复制文件,同时设置 PCL 的配置文件与版本隔离。
+    ''' 对路径末尾是否为 \ 没有要求。
+    ''' </summary>
+    Private Sub CopyOverrideDirectory(OverridesFolder As String, VersionFolder As String, Loader As LoaderBase, ProgressIncrement As Double)
+        If Not OverridesFolder.EndsWithF("\") Then OverridesFolder += "\"
+        If Not VersionFolder.EndsWithF("\") Then VersionFolder += "\"
+        '复制文件
+        If Directory.Exists(OverridesFolder) Then
+            Log($"[ModPack] 处理整合包覆写文件夹:{OverridesFolder} → {VersionFolder}")
+            CopyDirectory(OverridesFolder, VersionFolder, Sub(Delta) Loader.Progress += Delta * ProgressIncrement)
+        Else
+            Log($"[ModPack] 整合包中没有覆写文件夹:{OverridesFolder}")
+            Loader.Progress += ProgressIncrement
+        End If
+        '设置 ini
+        Dim OverridesIni As String = $"{OverridesFolder}PCL\Setup.ini"
+        Dim VersionIni As String = $"{VersionFolder}PCL\Setup.ini"
+        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) '开启版本隔离
+            WriteIni(VersionIni, "VersionArgumentIndieV2", True)
+        End If
+        IniClearCache(VersionIni) '重置缓存,避免被安装过程中写入的 ini 覆盖
+    End Sub
+    ''' <summary>
+    ''' 弹窗提示整合包中存在不兼容的加载器。
+    ''' 如果取消,则抛出 CancelledException。
+    ''' </summary>
+    ''' <exception cref="CancelledException" />
+    Private Sub NotifyIncompatibleLoader(LoaderName As String)
+        If MyMsgBox($"整合包中存在不兼容的加载器:{LoaderName}{vbCrLf}如果你知道如何手动安装它,可以先选择跳过,然后在整合包下载结束后手动安装。",
+            "不兼容的加载器", "取消", $"不安装 {LoaderName} 并继续") = 1 Then Throw New CancelledException
     End Sub
     End Sub
 
 
 #Region "不同类型整合包的安装方法"
 #Region "不同类型整合包的安装方法"
@@ -179,10 +220,11 @@ Retry:
                 Log("[ModPack] 整合包 Fabric 版本:" & Id)
                 Log("[ModPack] 整合包 Fabric 版本:" & Id)
                 FabricVersion = Id.Replace("fabric-", "")
                 FabricVersion = Id.Replace("fabric-", "")
             Else
             Else
-                Log("[ModPack] 未知 Mod 加载器:" & Id)
+                'ElseIf Id.StartsWithF("quilt-") Then
+                NotifyIncompatibleLoader(Id)
             End If
             End If
         Next
         Next
-        '解压与配置文件
+        '解压
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim OverrideHome As String = If(Json("overrides"), "")
         Dim OverrideHome As String = If(Json("overrides"), "")
@@ -190,21 +232,10 @@ Retry:
             InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
             InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
             Sub(Task As LoaderTask(Of String, Integer))
             Sub(Task As LoaderTask(Of String, Integer))
                 ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
                 ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
-                Task.Progress = 0.6
-                '复制结果
-                Log("[ModPack] 整合包 override 目标:" & OverrideHome)
-                Dim OverrideRoot = InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome) '#5613
-                If Directory.Exists(OverrideRoot) Then
-                    Dim CopyTarget = $"{PathMcFolder}versions\{VersionName}"
-                    CopyDirectory(OverrideRoot, CopyTarget, Sub(Delta) Task.Progress += Delta * 0.35)
-                    Log($"[ModPack] 整合包 override 复制:{OverrideRoot} -> {CopyTarget}")
-                Else
-                    Log($"[ModPack] 整合包中未找到 override 文件夹:{OverrideRoot}")
-                End If
-                Task.Progress = 0.95
-                '开启版本隔离
-                WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndie", 1)
-                WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndieV2", True)
+                CopyOverrideDirectory(
+                    InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome), '#5613
+                    $"{PathMcFolder}versions\{VersionName}",
+                    Task, 0.4)
             End Sub) With {
             End Sub) With {
             .ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
             .ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         End If
         End If
@@ -224,7 +255,7 @@ Retry:
             '获取 Mod 下载信息
             '获取 Mod 下载信息
             ModDownloadLoaders.Add(New LoaderTask(Of Integer, JArray)("获取 Mod 下载信息",
             ModDownloadLoaders.Add(New LoaderTask(Of Integer, JArray)("获取 Mod 下载信息",
             Sub(Task As LoaderTask(Of Integer, JArray))
             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 会跳过那一项
                 '如果文件已被删除,则 API 会跳过那一项
                 If ModList.Count > Task.Output.Count Then Throw New Exception("整合包中的部分 Mod 版本已被 Mod 作者删除,所以没法继续安装了,请向整合包作者反馈该问题")
                 If ModList.Count > Task.Output.Count Then Throw New Exception("整合包中的部分 Mod 版本已被 Mod 作者删除,所以没法继续安装了,请向整合包作者反馈该问题")
             End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s
             End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s
@@ -238,29 +269,28 @@ Retry:
                     If FileList.ContainsKey(Id) Then Continue For
                     If FileList.ContainsKey(Id) Then Continue For
                     '可选 Mod 提示
                     '可选 Mod 提示
                     If ModOptionalList.Contains(Id) Then
                     If ModOptionalList.Contains(Id) Then
-                        If MyMsgBox("是否要下载整合包中的可选文件 " & ModJson("displayName").ToString & "?",
-                                        "下载可选文件", "是", "否") = 2 Then
+                        If MyMsgBox("是否要下载整合包中的可选文件 " & ModJson("displayName").ToString & "?", "下载可选文件", "是", "否") = 2 Then
                             Continue For
                             Continue For
                         End If
                         End If
                     End If
                     End If
-                    '建立 CompFile
-                    Dim File As New CompFile(ModJson, CompType.Mod)
-                    If Not File.Available Then Continue For
                     '根据 modules 和文件名后缀判断资源类型
                     '根据 modules 和文件名后缀判断资源类型
-                    Dim TargetFolder As String
+                    Dim TargetFolder As String, Type As CompType
                     If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
                     If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
                         Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList
                         Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList
                         If ModuleNames.Contains("META-INF") OrElse ModuleNames.Contains("mcmod.info") OrElse
                         If ModuleNames.Contains("META-INF") OrElse ModuleNames.Contains("mcmod.info") OrElse
-                           File.FileName.EndsWithF(".jar", True) Then
-                            TargetFolder = "mods"
+                           ModJson?("FileName")?.ToString?.EndsWithF(".jar", True) Then
+                            TargetFolder = "mods" : Type = CompType.Mod
                         ElseIf ModuleNames.Contains("pack.mcmeta") Then
                         ElseIf ModuleNames.Contains("pack.mcmeta") Then
-                            TargetFolder = "resourcepacks"
+                            TargetFolder = "resourcepacks" : Type = CompType.ResourcePack
                         Else
                         Else
-                            TargetFolder = "shaderpacks"
+                            TargetFolder = "shaderpacks" : Type = CompType.Shader
                         End If
                         End If
                     Else
                     Else
-                        TargetFolder = "mods"
+                        TargetFolder = "mods" : Type = CompType.Mod
                     End If
                     End If
+                    '建立 CompFile
+                    Dim File As New CompFile(ModJson, Type)
+                    If Not File.Available Then Continue For
                     '实际的添加
                     '实际的添加
                     FileList.Add(Id, File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{TargetFolder}\"))
                     FileList.Add(Id, File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{TargetFolder}\"))
                     Task.Progress += 1 / (1 + ModList.Count)
                     Task.Progress += 1 / (1 + ModList.Count)
@@ -283,16 +313,11 @@ Retry:
             .NeoForgeVersion = NeoForgeVersion,
             .NeoForgeVersion = NeoForgeVersion,
             .FabricVersion = FabricVersion
             .FabricVersion = FabricVersion
         }
         }
-        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
-        '构造 Libraries 加载器
-        Dim LoadersLib As New List(Of LoaderBase)
-        LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
-        LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
+        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
         '构造总加载器
         '构造总加载器
         Dim Loaders As New List(Of LoaderBase)
         Dim Loaders As New List(Of LoaderBase)
         Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
         Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
         Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
         Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
-        Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
         Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
         Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
         Sub(Task As LoaderTask(Of String, String))
         Sub(Task As LoaderTask(Of String, String))
             '设置图标
             '设置图标
@@ -310,7 +335,7 @@ Retry:
                     File.Delete(Target)
                     File.Delete(Target)
                 End If
                 End If
             Next
             Next
-            If File.Exists(FileAddress) AndAlso GetFileNameWithoutExtentionFromPath(FileAddress) = "modpack" Then
+            If File.Exists(FileAddress) AndAlso IO.Path.GetFileNameWithoutExtension(FileAddress) = "modpack" Then
                 Log("[ModPack] 删除安装整合包文件:" & FileAddress)
                 Log("[ModPack] 删除安装整合包文件:" & FileAddress)
                 File.Delete(FileAddress)
                 File.Delete(FileAddress)
             End If
             End If
@@ -361,12 +386,9 @@ Retry:
                 Case "fabric-loader" 'eg. 0.14.14
                 Case "fabric-loader" 'eg. 0.14.14
                     FabricVersion = Entry.Value.ToString
                     FabricVersion = Entry.Value.ToString
                     Log("[ModPack] 整合包 Fabric 版本:" & FabricVersion)
                     Log("[ModPack] 整合包 Fabric 版本:" & FabricVersion)
-                Case "quilt-loader" 'eg. 1.0.0
-                    Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
-                    Throw New CancelledException
                 Case Else
                 Case Else
-                    Hint($"无法安装整合包,其中出现了未知的 Mod 加载器 {Entry.Value}!", HintType.Critical)
-                    Throw New CancelledException
+                    'Case "quilt-loader" 'eg. 1.0.0
+                    NotifyIncompatibleLoader(Entry.Name)
             End Select
             End Select
         Next
         Next
         '获取版本名
         '获取版本名
@@ -377,29 +399,20 @@ Retry:
             If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
             If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
             If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
             If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
         End If
         End If
-        '解压和配置文件
+        '解压
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         Sub(Task As LoaderTask(Of String, Integer))
         Sub(Task As LoaderTask(Of String, Integer))
-            ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
-            Task.Progress = 0.6
-            '复制 overrides 文件夹和 client-overrides 文件夹
-            If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
-                CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName,
-                    Sub(Delta) Task.Progress += Delta * 0.25)
-            Else
-                Log("[ModPack] 整合包中未找到 override 目录,已跳过")
-            End If
-            Task.Progress = 0.85
-            If Directory.Exists(InstallTemp & ArchiveBaseFolder & "client-overrides") Then
-                CopyDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", PathMcFolder & "versions\" & VersionName,
-                    Sub(Delta) Task.Progress += Delta * 0.1)
-            End If
-            Task.Progress = 0.95
-            '开启版本隔离
-            WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndie", 1)
-            WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndieV2", True)
+            ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.5)
+            CopyOverrideDirectory(
+                InstallTemp & ArchiveBaseFolder & "overrides",
+                PathMcFolder & "versions\" & VersionName,
+                Task, 0.4)
+            CopyOverrideDirectory(
+                InstallTemp & ArchiveBaseFolder & "client-overrides",
+                PathMcFolder & "versions\" & VersionName,
+                Task, 0.1)
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         '获取下载文件列表
         '获取下载文件列表
         Dim FileList As New List(Of NetFile)
         Dim FileList As New List(Of NetFile)
@@ -417,10 +430,15 @@ Retry:
                 End Select
                 End Select
             End If
             End If
             '添加下载文件
             '添加下载文件
-            Dim Urls = File("downloads").Select(Function(t) t.ToString.Replace("://edge.forgecdn", "://media.forgecdn")).ToList
+            Dim Urls = File("downloads").SelectMany(Function(t) CompFile.HandleCurseForgeDownloadUrls(t.ToString)).ToList
             Urls.AddRange(Urls.Select(Function(u) DlSourceModGet(u)).ToList)
             Urls.AddRange(Urls.Select(Function(u) DlSourceModGet(u)).ToList)
             Urls = Urls.Distinct.ToList()
             Urls = Urls.Distinct.ToList()
-            FileList.Add(New NetFile(Urls, PathMcFolder & "versions\" & VersionName & "\" & File("path").ToString,
+            Dim TargetPath As String = $"{PathMcFolder}versions\{VersionName}\{File("path")}"
+            If Not IO.Path.GetFullPath(TargetPath).StartsWithF($"{PathMcFolder}versions\{VersionName}\", True) Then
+                MyMsgBox($"整合包的文件路径超出了版本文件夹,请向整合包作者反馈此问题!" & vbCrLf & "错误的文件:" & TargetPath, "文件路径校验失败", IsWarn:=True)
+                Throw New CancelledException
+            End If
+            FileList.Add(New NetFile(Urls, TargetPath,
                 New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString), True))
                 New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString), True))
         Next
         Next
         If FileList.Any Then
         If FileList.Any Then
@@ -436,20 +454,15 @@ Retry:
             .NeoForgeVersion = NeoForgeVersion,
             .NeoForgeVersion = NeoForgeVersion,
             .FabricVersion = FabricVersion
             .FabricVersion = FabricVersion
         }
         }
-        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
-        '构造 Libraries 加载器
-        Dim LoadersLib As New List(Of LoaderBase)
-        LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
-        LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
+        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
         '构造总加载器
         '构造总加载器
         Dim Loaders As New List(Of LoaderBase)
         Dim Loaders As New List(Of LoaderBase)
         Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
         Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
         Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
         Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
-        Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
         Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
         Loaders.Add(New LoaderTask(Of String, String)("最终整理文件",
         Sub(Task As LoaderTask(Of String, String))
         Sub(Task As LoaderTask(Of String, String))
             '设置图标
             '设置图标
-            Dim VersionFolder As String = PathMcFolder & "versions\" & VersionName & "\"
+            Dim VersionFolder As String = $"{PathMcFolder}versions\{VersionName}\"
             If Logo IsNot Nothing AndAlso File.Exists(Logo) Then
             If Logo IsNot Nothing AndAlso File.Exists(Logo) Then
                 File.Copy(Logo, VersionFolder & "PCL\Logo.png", True)
                 File.Copy(Logo, VersionFolder & "PCL\Logo.png", True)
                 WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
                 WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png")
@@ -463,7 +476,7 @@ Retry:
                     File.Delete(Target)
                     File.Delete(Target)
                 End If
                 End If
             Next
             Next
-            If File.Exists(FileAddress) AndAlso GetFileNameWithoutExtentionFromPath(FileAddress) = "modpack" Then
+            If File.Exists(FileAddress) AndAlso IO.Path.GetFileNameWithoutExtension(FileAddress) = "modpack" Then
                 Log("[ModPack] 删除安装整合包文件:" & FileAddress)
                 Log("[ModPack] 删除安装整合包文件:" & FileAddress)
                 File.Delete(FileAddress)
                 File.Delete(FileAddress)
             End If
             End If
@@ -500,67 +513,38 @@ Retry:
         If Validate.Validate(VersionName) <> "" Then VersionName = ""
         If Validate.Validate(VersionName) <> "" Then VersionName = ""
         If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
         If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
         If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
         If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
-        '解压与配置文件
+        '解压
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         Sub(Task As LoaderTask(Of String, Integer))
         Sub(Task As LoaderTask(Of String, Integer))
             ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
             ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
-            Task.Progress = 0.6
-            '复制结果
-            If Directory.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then
-                CopyDirectory(InstallTemp & ArchiveBaseFolder & "minecraft", PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35)
-            Else
-                Log("[ModPack] 整合包中未找到 minecraft override 目录,已跳过")
-            End If
-            Task.Progress = 0.95
-            '开启版本隔离
-            WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndie", 1)
-            WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndieV2", True)
+            CopyOverrideDirectory(
+                InstallTemp & ArchiveBaseFolder & "minecraft",
+                PathMcFolder & "versions\" & VersionName,
+                Task, 0.4)
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
-        '构造加载器
+        '构造游戏本体安装加载器
         If Json("gameVersion") Is Nothing Then Throw New Exception("该 HMCL 整合包未提供游戏版本信息,无法安装!")
         If Json("gameVersion") Is Nothing Then Throw New Exception("该 HMCL 整合包未提供游戏版本信息,无法安装!")
         Dim Request As New McInstallRequest With {
         Dim Request As New McInstallRequest With {
             .TargetVersionName = VersionName,
             .TargetVersionName = VersionName,
             .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
             .MinecraftName = Json("gameVersion").ToString
             .MinecraftName = Json("gameVersion").ToString
         }
         }
-        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
-        '构造 Libraries 加载器(为了使得 Mods 下载结束后再构造,这样才会下载 JumpLoader 文件)
-        Dim LoadersLib As New List(Of LoaderBase)
-        LoadersLib.Add(New LoaderTask(Of String, String)("重命名版本 Json(副加载器)",
-        Sub()
-            Dim RealFileName As String = PathMcFolder & "versions\" & VersionName & "\" & VersionName & ".json"
-            Dim OldFileName As String = PathMcFolder & "versions\" & VersionName & "\pack.json"
-            If File.Exists(OldFileName) Then
-                '修改 id
-                Dim FileJson = GetJson(ReadFile(OldFileName))
-                FileJson("id") = VersionName
-                '替换文件
-                File.Delete(OldFileName)
-                WriteFile(RealFileName, FileJson.ToString)
-                Log("[ModPack] 已重命名版本 Json:" & RealFileName)
-            End If
-        End Sub) With {.ProgressWeight = 0.1, .Show = False})
-        LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
-        LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
+        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
         '构造总加载器
         '构造总加载器
         Dim Loaders As New List(Of LoaderBase) From {
         Dim Loaders As New List(Of LoaderBase) From {
-            New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)},
-            New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)},
-            New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}
+            New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)},
+            New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}
         }
         }
-
         '重复任务检查
         '重复任务检查
         Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " "
         Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " "
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
         If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
             Hint("该整合包正在安装中!", HintType.Critical)
             Hint("该整合包正在安装中!", HintType.Critical)
             Throw New CancelledException
             Throw New CancelledException
         End If
         End If
-
         '启动
         '启动
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
         Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState}
-        'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件”
         Loader.Start(Request.TargetVersionFolder)
         Loader.Start(Request.TargetVersionFolder)
         LoaderTaskbarAdd(Loader)
         LoaderTaskbarAdd(Loader)
         FrmMain.BtnExtraDownload.ShowRefresh()
         FrmMain.BtnExtraDownload.ShowRefresh()
@@ -584,24 +568,17 @@ Retry:
         If Validate.Validate(VersionName) <> "" Then VersionName = ""
         If Validate.Validate(VersionName) <> "" Then VersionName = ""
         If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
         If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
         If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
         If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
-        '解压、配置设置文件
+        '解压
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim SetupFile As String = $"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini"
         Dim SetupFile As String = $"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini"
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         Sub(Task As LoaderTask(Of String, Integer))
         Sub(Task As LoaderTask(Of String, Integer))
-            ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
-            Task.Progress = 0.6
-            '复制结果
-            If Directory.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") Then
-                CopyDirectory(InstallTemp & ArchiveBaseFolder & ".minecraft", PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35)
-            Else
-                Log("[ModPack] 整合包中未找到 override .minecraft 目录,已跳过")
-            End If
-            Task.Progress = 0.95
-            '开启版本隔离
-            WriteIni(SetupFile, "VersionArgumentIndie", 1)
-            WriteIni(SetupFile, "VersionArgumentIndieV2", True)
+            ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.55)
+            CopyOverrideDirectory(
+                InstallTemp & ArchiveBaseFolder & ".minecraft",
+                PathMcFolder & "versions\" & VersionName,
+                Task, 0.4)
             '读取 MMC 设置文件(#2655)
             '读取 MMC 设置文件(#2655)
             Try
             Try
                 Dim MMCSetupFile As String = InstallTemp & ArchiveBaseFolder & "instance.cfg"
                 Dim MMCSetupFile As String = InstallTemp & ArchiveBaseFolder & "instance.cfg"
@@ -663,7 +640,8 @@ Retry:
         If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!")
         If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!")
         Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"}
         Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"}
         For Each Component In PackJson("components")
         For Each Component In PackJson("components")
-            Select Case If(Component("uid"), "").ToString
+            If Not Component.Contains("uid") Then Continue For
+            Select Case Component("uid").ToString
                 Case "org.lwjgl"
                 Case "org.lwjgl"
                     Log("[ModPack] 已跳过 LWJGL 项")
                     Log("[ModPack] 已跳过 LWJGL 项")
                 Case "net.minecraft"
                 Case "net.minecraft"
@@ -674,22 +652,17 @@ Retry:
                     Request.NeoForgeVersion = Component("version")
                     Request.NeoForgeVersion = Component("version")
                 Case "net.fabricmc.fabric-loader"
                 Case "net.fabricmc.fabric-loader"
                     Request.FabricVersion = Component("version")
                     Request.FabricVersion = Component("version")
-                Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
-                    Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
-                    Throw New CancelledException
+                Case Else
+                    'Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
+                    NotifyIncompatibleLoader(Component("uid"))
             End Select
             End Select
         Next
         Next
         '构造加载器
         '构造加载器
-        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
-        '构造 Libraries 加载器
-        Dim LoadersLib As New List(Of LoaderBase)
-        LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
-        LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
+        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
         '构造总加载器
         '构造总加载器
         Dim Loaders As New List(Of LoaderBase)
         Dim Loaders As New List(Of LoaderBase)
-        Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
-        Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
-        Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
+        Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
+        Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
 
 
         '重复任务检查
         '重复任务检查
         Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " "
         Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " "
@@ -726,24 +699,23 @@ Retry:
             If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
             If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
             If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
             If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
         End If
         End If
-        '解压与配置文件
+        '解压
         Dim InstallTemp As String = RequestTaskTempFolder()
         Dim InstallTemp As String = RequestTaskTempFolder()
+        Dim SetupFile As String = $"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini"
         Dim InstallLoaders As New List(Of LoaderBase)
         Dim InstallLoaders As New List(Of LoaderBase)
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
         Sub(Task As LoaderTask(Of String, Integer))
         Sub(Task As LoaderTask(Of String, Integer))
             ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
             ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
-            Task.Progress = 0.6
-            '复制结果
-            If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
-                CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName,
-                    Sub(Delta) Task.Progress += 0.35 * Delta)
-            Else
-                Log("[ModPack] 整合包中未找到 overrides 目录,已跳过")
+            CopyOverrideDirectory(
+                InstallTemp & ArchiveBaseFolder & "overrides",
+                PathMcFolder & "versions\" & VersionName,
+                Task, 0.4)
+            'JVM 参数
+            If Json("launchInfo") IsNot Nothing Then
+                Dim LaunchInfo As JObject = Json("launchInfo")
+                If LaunchInfo.ContainsKey("javaArgument") Then WriteIni(SetupFile, "VersionAdvanceJvm", String.Join(" ", LaunchInfo("javaArgument")))
+                If LaunchInfo.ContainsKey("launchArgument") Then WriteIni(SetupFile, "VersionAdvanceGame", String.Join(" ", LaunchInfo("launchArgument")))
             End If
             End If
-            Task.Progress = 0.95
-            '开启版本隔离
-            WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndie", 1)
-            WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndieV2", True)
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
         '构造加载器
         '构造加载器
         If Json("addons") Is Nothing Then Throw New Exception("该 MCBBS 整合包未提供游戏版本附加信息,无法安装!")
         If Json("addons") Is Nothing Then Throw New Exception("该 MCBBS 整合包未提供游戏版本附加信息,无法安装!")
@@ -765,16 +737,11 @@ Retry:
             .NeoForgeVersion = If(Addons.ContainsKey("neoforge"), Addons("neoforge"), Nothing),
             .NeoForgeVersion = If(Addons.ContainsKey("neoforge"), Addons("neoforge"), Nothing),
             .FabricVersion = If(Addons.ContainsKey("fabric"), Addons("fabric"), Nothing)
             .FabricVersion = If(Addons.ContainsKey("fabric"), Addons("fabric"), Nothing)
         }
         }
-        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
-        '构造 Libraries 加载器
-        Dim LoadersLib As New List(Of LoaderBase)
-        LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False})
-        LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
+        Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request)
         '构造总加载器
         '构造总加载器
         Dim Loaders As New List(Of LoaderBase)
         Dim Loaders As New List(Of LoaderBase)
-        Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
-        Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
-        Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
+        Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)})
+        Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)})
 
 
         '重复任务检查
         '重复任务检查
         Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " "
         Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " "
@@ -828,7 +795,7 @@ Retry:
                     Log("[Modpack] 找到压缩包中附带的启动器:" & Launcher)
                     Log("[Modpack] 找到压缩包中附带的启动器:" & Launcher)
                     If MyMsgBox($"整合包里似乎自带了启动器,是否换用它继续安装?{vbCrLf}即将打开:{Launcher}", "换用整合包启动器?", "换用", "不换用") = 1 Then
                     If MyMsgBox($"整合包里似乎自带了启动器,是否换用它继续安装?{vbCrLf}即将打开:{Launcher}", "换用整合包启动器?", "换用", "不换用") = 1 Then
                         OpenExplorer(TargetFolder)
                         OpenExplorer(TargetFolder)
-                        ShellOnly(Launcher, "--wait") '要求等待已有的 PCL 退出
+                        StartProcess(Launcher, "--wait") '要求等待已有的 PCL 退出
                         Log("[Modpack] 为换用整合包中的启动器启动,强制结束程序")
                         Log("[Modpack] 为换用整合包中的启动器启动,强制结束程序")
                         FrmMain.EndProgram(False)
                         FrmMain.EndProgram(False)
                         Return
                         Return

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

@@ -15,7 +15,7 @@
                 TriggerLauncherShutdown = False
                 TriggerLauncherShutdown = False
             End If
             End If
         Next
         Next
-        If IsWatcherRunning = IsRunning Then Exit Sub
+        If IsWatcherRunning = IsRunning Then Return
         IsWatcherRunning = IsRunning
         IsWatcherRunning = IsRunning
         If IsWatcherRunning Then
         If IsWatcherRunning Then
             MinecraftStart()
             MinecraftStart()
@@ -81,7 +81,7 @@
             WatcherStateChanged()
             WatcherStateChanged()
 
 
             '初始化进程与日志读取
             '初始化进程与日志读取
-            Me.GameProcess = Loader.Input
+            GameProcess = Loader.Input
             GameProcess.BeginOutputReadLine()
             GameProcess.BeginOutputReadLine()
             GameProcess.BeginErrorReadLine()
             GameProcess.BeginErrorReadLine()
             AddHandler GameProcess.OutputDataReceived, AddressOf LogReceived
             AddHandler GameProcess.OutputDataReceived, AddressOf LogReceived
@@ -118,7 +118,7 @@
                 Return _State
                 Return _State
             End Get
             End Get
             Set(value As MinecraftState)
             Set(value As MinecraftState)
-                If _State = value Then Exit Property
+                If _State = value Then Return
                 _State = value
                 _State = value
                 WatcherStateChanged()
                 WatcherStateChanged()
             End Set
             End Set
@@ -144,7 +144,7 @@
                 '输出文本
                 '输出文本
                 Dim Copyed As New List(Of String)
                 Dim Copyed As New List(Of String)
                 SyncLock WaitingLogLock
                 SyncLock WaitingLogLock
-                    If Not WaitingLog.Any() Then Exit Sub
+                    If Not WaitingLog.Any() Then Return
                     Copyed = WaitingLog
                     Copyed = WaitingLog
                     WaitingLog = New List(Of String)(1000)
                     WaitingLog = New List(Of String)(1000)
                 End SyncLock
                 End SyncLock
@@ -180,8 +180,9 @@
         Public LatestLog As New Queue(Of String)
         Public LatestLog As New Queue(Of String)
         Private Sub GameLog(Text As String)
         Private Sub GameLog(Text As String)
             '预处理
             '预处理
-            If Text Is Nothing Then Exit Sub
+            If Text Is Nothing Then Return
             Text = Text.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, vbCrLf)
             Text = Text.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, vbCrLf)
+            'If Text.Contains("�����") Then Hint("检测到错误的日志编码:" & Text)
             '加入预存储
             '加入预存储
             LatestLog.Enqueue(Text)
             LatestLog.Enqueue(Text)
             If LatestLog.Count >= 501 Then LatestLog.Dequeue()
             If LatestLog.Count >= 501 Then LatestLog.Dequeue()
@@ -256,8 +257,8 @@
         Private WindowHandle As IntPtr
         Private WindowHandle As IntPtr
         Private Sub TimerWindow()
         Private Sub TimerWindow()
             Try
             Try
-                If GameProcess.HasExited Then Exit Sub
-                If IsWindowFinished Then Exit Sub
+                If GameProcess.HasExited Then Return
+                If IsWindowFinished Then Return
                 '获取全部窗口,检查是否有新增的
                 '获取全部窗口,检查是否有新增的
                 Dim MinecraftWindow As KeyValuePair(Of IntPtr, String)? = Nothing
                 Dim MinecraftWindow As KeyValuePair(Of IntPtr, String)? = Nothing
                 Try
                 Try
@@ -267,10 +268,10 @@
                     Log(ex, "由于反作弊或安全软件拦截,PCL 无法操作游戏窗口", LogLevel.Hint)
                     Log(ex, "由于反作弊或安全软件拦截,PCL 无法操作游戏窗口", LogLevel.Hint)
                     IsWindowFinished = True
                     IsWindowFinished = True
                 End Try
                 End Try
-                If MinecraftWindow Is Nothing Then Exit Sub
+                If MinecraftWindow Is Nothing Then Return
                 Dim MinecraftWindowName = MinecraftWindow.Value.Value, MinecraftWindowHandle = MinecraftWindow.Value.Key
                 Dim MinecraftWindowName = MinecraftWindow.Value.Value, MinecraftWindowHandle = MinecraftWindow.Value.Key
                 '已找到窗口
                 '已找到窗口
-                If Not MinecraftWindowName.StartsWithF("FML") Then
+                If Not MinecraftWindowName.StartsWithF("FML") AndAlso Not MinecraftWindowName.StartsWithF("Quilt Loader") Then
                     '已找到 Minecraft 窗口
                     '已找到 Minecraft 窗口
                     WindowHandle = MinecraftWindowHandle
                     WindowHandle = MinecraftWindowHandle
                     WatcherLog($"Minecraft 窗口已加载:{MinecraftWindowName}({MinecraftWindowHandle.ToInt64})")
                     WatcherLog($"Minecraft 窗口已加载:{MinecraftWindowName}({MinecraftWindowHandle.ToInt64})")
@@ -291,8 +292,8 @@
                         End Sub, "MinecraftWindowMaximize")
                         End Sub, "MinecraftWindowMaximize")
                     End If
                     End If
                 ElseIf Not IsWindowAppeared Then
                 ElseIf Not IsWindowAppeared Then
-                    '已找到 FML 窗口
-                    WatcherLog("FML 窗口已加载:" & MinecraftWindowName & "(" & MinecraftWindowHandle.ToInt64 & ")")
+                    '已找到 Mod Loader 窗口
+                    WatcherLog("Mod Loader 窗口已加载:" & MinecraftWindowName & "(" & MinecraftWindowHandle.ToInt64 & ")")
                 End If
                 End If
                 IsWindowAppeared = True
                 IsWindowAppeared = True
             Catch ex As Exception
             Catch ex As Exception
@@ -307,27 +308,27 @@
             TryGetMinecraftWindow = Nothing
             TryGetMinecraftWindow = Nothing
             EnumWindows(
             EnumWindows(
                 Sub(hwnd As IntPtr, lParam As Integer)
                 Sub(hwnd As IntPtr, lParam As Integer)
-                    If TryGetMinecraftWindow IsNot Nothing Then Exit Sub
+                    If TryGetMinecraftWindow IsNot Nothing Then Return
                     '检查类名
                     '检查类名
                     Dim str As New StringBuilder(512)
                     Dim str As New StringBuilder(512)
                     GetClassName(hwnd, str, str.Capacity)
                     GetClassName(hwnd, str, str.Capacity)
                     Dim ClassName As String = str.ToString
                     Dim ClassName As String = str.ToString
-                    If Not (ClassName = "GLFW30" OrElse ClassName = "LWJGL" OrElse ClassName = "SunAwtFrame") Then Exit Sub
+                    If Not (ClassName = "GLFW30" OrElse ClassName = "LWJGL" OrElse ClassName = "SunAwtFrame") Then Return
                     '获取窗口标题名
                     '获取窗口标题名
                     str = New StringBuilder(512)
                     str = New StringBuilder(512)
                     GetWindowText(hwnd, str, str.Capacity)
                     GetWindowText(hwnd, str, str.Capacity)
                     Dim WindowText As String = str.ToString
                     Dim WindowText As String = str.ToString
                     '有的 Mod 可以修改窗口标题,所以不能检测是否为 Minecraft 打头,这并不准确
                     '有的 Mod 可以修改窗口标题,所以不能检测是否为 Minecraft 打头,这并不准确
                     '部分版本会搞个 GLFW message window 出来所以得反选
                     '部分版本会搞个 GLFW message window 出来所以得反选
-                    If Not (WindowText.StartsWithF("FML") OrElse (WindowText <> "PopupMessageWindow") AndAlso Not WindowText.StartsWithF("GLFW")) Then Exit Sub
+                    If Not (WindowText.StartsWithF("FML") OrElse (WindowText <> "PopupMessageWindow") AndAlso Not WindowText.StartsWithF("GLFW")) Then Return
                     '获取窗口关联的进程
                     '获取窗口关联的进程
                     Dim ProcessId As Integer
                     Dim ProcessId As Integer
                     GetWindowThreadProcessId(hwnd, ProcessId)
                     GetWindowThreadProcessId(hwnd, ProcessId)
                     Try
                     Try
-                        If Process.GetProcessById(ProcessId).StartTime < GameProcess.StartTime Then Exit Sub '需要是此后启动的进程
+                        If Process.GetProcessById(ProcessId).StartTime < GameProcess.StartTime Then Return '需要是此后启动的进程
                     Catch ex As Exception
                     Catch ex As Exception
                         Log(ex, "枚举 Minecraft 窗口进程失败")
                         Log(ex, "枚举 Minecraft 窗口进程失败")
-                        Exit Sub
+                        Return
                     End Try
                     End Try
                     '返回
                     '返回
                     TryGetMinecraftWindow = New KeyValuePair(Of IntPtr, String)(hwnd, WindowText)
                     TryGetMinecraftWindow = New KeyValuePair(Of IntPtr, String)(hwnd, WindowText)
@@ -344,18 +345,18 @@
 
 
         '崩溃处理
         '崩溃处理
         Private Sub Crashed()
         Private Sub Crashed()
-            If State = MinecraftState.Crashed OrElse State = MinecraftState.Ended Then Exit Sub
+            If State = MinecraftState.Crashed OrElse State = MinecraftState.Ended Then Return
             State = MinecraftState.Crashed
             State = MinecraftState.Crashed
             '崩溃分析
             '崩溃分析
             WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析")
             WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析")
-            Hint("检测到 Minecraft 出现错误,错误分析已开始……")
+            Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Critical)
             FeedbackInfo()
             FeedbackInfo()
             RunInNewThread(
             RunInNewThread(
             Sub()
             Sub()
                 Try
                 Try
                     Thread.Sleep(2000)
                     Thread.Sleep(2000)
                     WatcherLog("崩溃分析开始")
                     WatcherLog("崩溃分析开始")
-                    Dim Analyzer As New CrashAnalyzer(PID)
+                    Dim Analyzer As New CrashAnalyzer(Version)
                     Analyzer.Collect(Version.PathIndie, LatestLog.ToList)
                     Analyzer.Collect(Version.PathIndie, LatestLog.ToList)
                     Analyzer.Prepare()
                     Analyzer.Prepare()
                     Analyzer.Analyze(Version)
                     Analyzer.Analyze(Version)

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

@@ -1,4 +1,4 @@
-Imports System.Collections.ObjectModel
+Imports System.Collections.ObjectModel
 
 
 Public Module ModEvent
 Public Module ModEvent
 #Region "事件执行方法"
 #Region "事件执行方法"

+ 22 - 24
Plain Craft Launcher 2/Modules/ModMain.vb

@@ -34,12 +34,12 @@ Public Module ModMain
     ''' <summary>
     ''' <summary>
     ''' 等待弹出的提示列表。以 {String, HintType, Log As Boolean} 形式存储为数组。
     ''' 等待弹出的提示列表。以 {String, HintType, Log As Boolean} 形式存储为数组。
     ''' </summary>
     ''' </summary>
-    Private HintWaiting As List(Of HintMessage) = If(HintWaiting, New List(Of HintMessage))
+    Private HintWaiting As SafeList(Of HintMessage) = If(HintWaiting, New SafeList(Of HintMessage))
     ''' <summary>
     ''' <summary>
     ''' 在窗口左下角弹出提示文本。
     ''' 在窗口左下角弹出提示文本。
     ''' </summary>
     ''' </summary>
     Public Sub Hint(Text As String, Optional Type As HintType = HintType.Info, Optional Log As Boolean = True)
     Public Sub Hint(Text As String, Optional Type As HintType = HintType.Info, Optional Log As Boolean = True)
-        If HintWaiting Is Nothing Then HintWaiting = New List(Of HintMessage)
+        If HintWaiting Is Nothing Then HintWaiting = New SafeList(Of HintMessage)
         HintWaiting.Add(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log})
         HintWaiting.Add(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log})
     End Sub
     End Sub
 
 
@@ -47,7 +47,7 @@ Public Module ModMain
         Try
         Try
 
 
             'Tag 存储了:{ 是否可以重用, Uuid }
             'Tag 存储了:{ 是否可以重用, Uuid }
-            If Not HintWaiting.Any() Then Exit Sub
+            If Not HintWaiting.Any() Then Return
             Do While HintWaiting.Any
             Do While HintWaiting.Any
                 ''清除空提示
                 ''清除空提示
                 'If IsNothing(HintWaiting(0)) OrElse IsNothing(HintWaiting(0)(0)) Then
                 'If IsNothing(HintWaiting(0)) OrElse IsNothing(HintWaiting(0)(0)) Then
@@ -343,7 +343,7 @@ EndHint:
     Public WaitingMyMsgBox As List(Of MyMsgBoxConverter) = If(WaitingMyMsgBox, New List(Of MyMsgBoxConverter))
     Public WaitingMyMsgBox As List(Of MyMsgBoxConverter) = If(WaitingMyMsgBox, New List(Of MyMsgBoxConverter))
     Public Sub MyMsgBoxTick()
     Public Sub MyMsgBoxTick()
         Try
         Try
-            If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing OrElse FrmMain.WindowState = WindowState.Minimized Then Exit Sub
+            If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing OrElse FrmMain.WindowState = WindowState.Minimized Then Return
             If FrmMain.PanMsg.Children.Count > 0 Then
             If FrmMain.PanMsg.Children.Count > 0 Then
                 '弹窗中
                 '弹窗中
                 FrmMain.PanMsg.Visibility = Visibility.Visible
                 FrmMain.PanMsg.Visibility = Visibility.Visible
@@ -397,14 +397,11 @@ EndHint:
     '下载页面声明
     '下载页面声明
     Public FrmDownloadLeft As PageDownloadLeft
     Public FrmDownloadLeft As PageDownloadLeft
     Public FrmDownloadInstall As PageDownloadInstall
     Public FrmDownloadInstall As PageDownloadInstall
-    Public FrmDownloadClient As PageDownloadClient
-    Public FrmDownloadOptiFine As PageDownloadOptiFine
-    Public FrmDownloadLiteLoader As PageDownloadLiteLoader
-    Public FrmDownloadForge As PageDownloadForge
-    Public FrmDownloadNeoForge As PageDownloadNeoForge
-    Public FrmDownloadFabric As PageDownloadFabric
     Public FrmDownloadMod As PageDownloadMod
     Public FrmDownloadMod As PageDownloadMod
     Public FrmDownloadPack As PageDownloadPack
     Public FrmDownloadPack As PageDownloadPack
+    Public FrmDownloadDataPack As PageDownloadDataPack
+    Public FrmDownloadShader As PageDownloadShader
+    Public FrmDownloadResourcePack As PageDownloadResourcePack
 
 
     '设置页面声明
     '设置页面声明
     Public FrmSetupLeft As PageSetupLeft
     Public FrmSetupLeft As PageSetupLeft
@@ -634,7 +631,7 @@ NextFile:
                 Catch ex As Exception
                 Catch ex As Exception
                     Log(ex, "检查帮助文件夹失败", LogLevel.Msgbox)
                     Log(ex, "检查帮助文件夹失败", LogLevel.Msgbox)
                 End Try
                 End Try
-                If Loader.IsAborted Then Exit Sub
+                If Loader.IsAborted Then Return
 
 
                 '将文件实例化
                 '将文件实例化
                 Dim Dict As New List(Of HelpEntry)
                 Dim Dict As New List(Of HelpEntry)
@@ -650,7 +647,7 @@ NextFile:
 
 
                 '回设
                 '回设
                 If Not Dict.Any() Then Throw New Exception("未找到可用的帮助;若不需要帮助页面,可以在 设置 → 个性化 → 功能隐藏 中将其隐藏")
                 If Not Dict.Any() Then Throw New Exception("未找到可用的帮助;若不需要帮助页面,可以在 设置 → 个性化 → 功能隐藏 中将其隐藏")
-                If Loader.IsAborted Then Exit Sub
+                If Loader.IsAborted Then Return
                 Loader.Output = Dict
                 Loader.Output = Dict
 
 
             Catch ex As Exception
             Catch ex As Exception
@@ -693,8 +690,8 @@ NextFile:
     Private AprilDistance As Integer = 0
     Private AprilDistance As Integer = 0
     Private Sub TimerFool()
     Private Sub TimerFool()
         Try
         Try
-            If FrmLaunchLeft Is Nothing OrElse FrmLaunchLeft.AprilPosTrans Is Nothing OrElse FrmMain.lastMouseArg Is Nothing Then Exit Sub
-            If IsAprilGiveup OrElse FrmMain.PageCurrent <> FormMain.PageType.Launch OrElse AniControlEnabled <> 0 OrElse Not FrmLaunchLeft.BtnLaunch.IsLoaded Then Exit Sub
+            If FrmLaunchLeft Is Nothing OrElse FrmLaunchLeft.AprilPosTrans Is Nothing OrElse FrmMain.lastMouseArg Is Nothing Then Return
+            If IsAprilGiveup OrElse FrmMain.PageCurrent <> FormMain.PageType.Launch OrElse AniControlEnabled <> 0 OrElse Not FrmLaunchLeft.BtnLaunch.IsLoaded Then Return
 
 
             '计算是否空闲
             '计算是否空闲
             Dim MousePos = FrmMain.lastMouseArg.GetPosition(FrmMain)
             Dim MousePos = FrmMain.lastMouseArg.GetPosition(FrmMain)
@@ -763,7 +760,7 @@ NextFile:
             '移动
             '移动
             AprilSpeed = AprilSpeed * 0.8 + Acc
             AprilSpeed = AprilSpeed * 0.8 + Acc
             Dim SpeedValue = Math.Min(60, AprilSpeed.Length)
             Dim SpeedValue = Math.Min(60, AprilSpeed.Length)
-            If SpeedValue < 0.01 Then Exit Sub
+            If SpeedValue < 0.01 Then Return
             AprilSpeed.Normalize()
             AprilSpeed.Normalize()
             AprilSpeed *= SpeedValue
             AprilSpeed *= SpeedValue
             AprilDistance += SpeedValue
             AprilDistance += SpeedValue
@@ -910,7 +907,7 @@ NextFile:
             If ThemeDontClick = 2 Then ThemeRefresh()
             If ThemeDontClick = 2 Then ThemeRefresh()
 #End Region
 #End Region
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "短程主时钟执行异常", LogLevel.Assert)
+            Log(ex, "短程主时钟执行异常", LogLevel.Critical)
         End Try
         End Try
         Timer4Count += 1
         Timer4Count += 1
         If Timer4Count = 4 Then
         If Timer4Count = 4 Then
@@ -930,15 +927,16 @@ NextFile:
 #Region "每 7.5s 执行一次的代码"
 #Region "每 7.5s 执行一次的代码"
                 If FrmMain.BtnExtraApril_ShowCheck AndAlso AprilDistance <> 0 Then FrmMain.BtnExtraApril.Ribble()
                 If FrmMain.BtnExtraApril_ShowCheck AndAlso AprilDistance <> 0 Then FrmMain.BtnExtraApril.Ribble()
                 '以未知原因窗口被丢到一边去的修复(Top、Left = -25600),还有 #745
                 '以未知原因窗口被丢到一边去的修复(Top、Left = -25600),还有 #745
-                RunInUi(Sub()
-                            If Not FrmMain.Hidden Then
-                                If FrmMain.Top < -9000 Then FrmMain.Top = 100
-                                If FrmMain.Left < -9000 Then FrmMain.Left = 100 '窗口拉至最大时 Left = -18.8
-                            End If
-                        End Sub)
+                RunInUi(
+                Sub()
+                    If Not FrmMain.Hidden Then
+                        If FrmMain.Top < -9000 Then FrmMain.Top = 100
+                        If FrmMain.Left < -9000 Then FrmMain.Left = 100 '窗口拉至最大时 Left = -18.8
+                    End If
+                End Sub)
 #End Region
 #End Region
             Catch ex As Exception
             Catch ex As Exception
-                Log(ex, "长程主时钟执行异常", LogLevel.Assert)
+                Log(ex, "长程主时钟执行异常", LogLevel.Critical)
             End Try
             End Try
         End If
         End If
     End Sub
     End Sub
@@ -954,7 +952,7 @@ NextFile:
                 Log(ex, "程序主时钟出错", LogLevel.Feedback)
                 Log(ex, "程序主时钟出错", LogLevel.Feedback)
             End Try
             End Try
         End Sub, "Timer Main")
         End Sub, "Timer Main")
-        If Not IsAprilEnabled Then Exit Sub
+        If Not IsAprilEnabled Then Return
         RunInNewThread(
         RunInNewThread(
         Sub()
         Sub()
             Try
             Try

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

@@ -79,7 +79,7 @@
                     If MusicState = MusicStates.Pause Then
                     If MusicState = MusicStates.Pause Then
                         FrmMain.BtnExtraMusic.Logo = Logo.IconPlay
                         FrmMain.BtnExtraMusic.Logo = Logo.IconPlay
                         FrmMain.BtnExtraMusic.LogoScale = 0.8
                         FrmMain.BtnExtraMusic.LogoScale = 0.8
-                        ToolTipText = "已暂停:" & GetFileNameWithoutExtentionFromPath(MusicCurrent)
+                        ToolTipText = "已暂停:" & IO.Path.GetFileNameWithoutExtension(MusicCurrent)
                         If MusicAllList.Count > 1 Then
                         If MusicAllList.Count > 1 Then
                             ToolTipText += vbCrLf & "左键恢复播放,右键播放下一曲。"
                             ToolTipText += vbCrLf & "左键恢复播放,右键播放下一曲。"
                         Else
                         Else
@@ -88,7 +88,7 @@
                     Else
                     Else
                         FrmMain.BtnExtraMusic.Logo = Logo.IconMusic
                         FrmMain.BtnExtraMusic.Logo = Logo.IconMusic
                         FrmMain.BtnExtraMusic.LogoScale = 1
                         FrmMain.BtnExtraMusic.LogoScale = 1
-                        ToolTipText = "正在播放:" & GetFileNameWithoutExtentionFromPath(MusicCurrent)
+                        ToolTipText = "正在播放:" & IO.Path.GetFileNameWithoutExtension(MusicCurrent)
                         If MusicAllList.Count > 1 Then
                         If MusicAllList.Count > 1 Then
                             ToolTipText += vbCrLf & "左键暂停,右键播放下一曲。"
                             ToolTipText += vbCrLf & "左键暂停,右键播放下一曲。"
                         Else
                         Else
@@ -247,7 +247,7 @@
                 Log("[Music] 已恢复播放")
                 Log("[Music] 已恢复播放")
                 Try
                 Try
                     MusicNAudio?.Play()
                     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?.Stop()
                     MusicNAudio?.Play()
                     MusicNAudio?.Play()
                 End Try
                 End Try

+ 69 - 74
Plain Craft Launcher 2/Modules/ModSecret.vb

@@ -9,9 +9,11 @@ Friend Module ModSecret
     '在开源版的注册表与常规版的注册表隔离,以防数据冲突
     '在开源版的注册表与常规版的注册表隔离,以防数据冲突
     Public Const RegFolder As String = "PCLDebug"
     Public Const RegFolder As String = "PCLDebug"
     '用于微软登录的 ClientId
     '用于微软登录的 ClientId
-    Public Const OAuthClientId As String = ""
+    Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "")
     'CurseForge API Key
     '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()
     Friend Sub SecretOnApplicationStart()
         '提升 UI 线程优先级
         '提升 UI 线程优先级
@@ -67,61 +69,29 @@ Friend Module ModSecret
         If Not DataList.Any(Function(d) d.Contains("-Dlog4j2.formatMsgNoLookups=true")) Then DataList.Add("-Dlog4j2.formatMsgNoLookups=true")
         If Not DataList.Any(Function(d) d.Contains("-Dlog4j2.formatMsgNoLookups=true")) Then DataList.Add("-Dlog4j2.formatMsgNoLookups=true")
     End Sub
     End Sub
 
 
-    ''' <summary>
-    ''' 打码字符串中的 AccessToken。
-    ''' </summary>
-    Friend Function SecretFilter(Raw As String, FilterChar As Char) As String
-        '打码 "accessToken " 后的内容
-        If Raw.Contains("accessToken ") Then
-            For Each Token In RegexSearch(Raw, "(?<=accessToken ([^ ]{5}))[^ ]+(?=[^ ]{5})")
-                Raw = Raw.Replace(Token, New String(FilterChar, Token.Count))
-            Next
-        End If
-        '打码当前登录的结果
-        Dim AccessToken As String = McLoginLoader.Output.AccessToken
-        If AccessToken Is Nothing OrElse AccessToken.Length < 10 OrElse Not Raw.ContainsF(AccessToken, True) OrElse
-            McLoginLoader.Output.Uuid = McLoginLoader.Output.AccessToken Then 'UUID 和 AccessToken 一样则不打码
-            Return Raw
-        Else
-            Return Raw.Replace(AccessToken, Left(AccessToken, 5) & New String(FilterChar, AccessToken.Length - 10) & Right(AccessToken, 5))
-        End If
-    End Function
-
 #End Region
 #End Region
 
 
 #Region "网络鉴权"
 #Region "网络鉴权"
 
 
-    Friend Function SecretCdnSign(UrlWithMark As String)
+    Friend Function SecretCdnSign(UrlWithMark As String) As String
         If Not UrlWithMark.EndsWithF("{CDN}") Then Return UrlWithMark
         If Not UrlWithMark.EndsWithF("{CDN}") Then Return UrlWithMark
         Return UrlWithMark.Replace("{CDN}", "").Replace(" ", "%20")
         Return UrlWithMark.Replace("{CDN}", "").Replace(" ", "%20")
     End Function
     End Function
     ''' <summary>
     ''' <summary>
     ''' 设置 Headers 的 UA、Referer。
     ''' 设置 Headers 的 UA、Referer。
     ''' </summary>
     ''' </summary>
-    Friend Sub SecretHeadersSign(Url As String, ByRef Client As WebClient, Optional UseBrowserUserAgent As Boolean = False)
-        If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then
-            Client.Headers("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"
-        Else
-            Client.Headers("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
+    Friend Sub SecretHeadersSign(Url As String, ByRef Req As HttpRequestMessage, Optional SimulateBrowserHeaders As Boolean = False)
+        If Not Req.Headers.UserAgent.Any Then
+            If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then
+                Req.Headers.Add("User-Agent", "LogStatistic")  '#4951
+            ElseIf SimulateBrowserHeaders Then
+                Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode} Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36")
+            Else
+                Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode}")
+            End If
         End If
         End If
-        Request.Referer = "http://" & VersionCode & ".open.pcl2.server/"
-        If Url.Contains("api.curseforge.com") Then Request.Headers("x-api-key") = CurseForgeAPIKey
+        If Not SimulateBrowserHeaders Then 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 Sub
 
 
 #End Region
 #End Region
@@ -198,45 +168,70 @@ Friend Module ModSecret
     Public ThemeDontClick As Integer = 0
     Public ThemeDontClick As Integer = 0
 
 
     Public Sub ThemeRefresh(Optional NewTheme As Integer = -1)
     Public Sub ThemeRefresh(Optional NewTheme As Integer = -1)
-        Hint("该版本中不包含主题功能……")
+        Try
+            If ThemeNow = NewTheme AndAlso NewTheme >= 0 Then Return
+            If NewTheme >= 0 Then ThemeNow = NewTheme
+
+            Color1 = New MyColor().FromHSL2(ColorHue, ColorSat * 0.2, 25 + ColorLightAdjust * 0.3)
+            Color2 = New MyColor().FromHSL2(ColorHue, ColorSat, 45 + ColorLightAdjust)
+            Color3 = New MyColor().FromHSL2(ColorHue, ColorSat, 55 + ColorLightAdjust)
+            Color4 = New MyColor().FromHSL2(ColorHue, ColorSat, 65 + ColorLightAdjust)
+            Color5 = New MyColor().FromHSL2(ColorHue, ColorSat, 80 + ColorLightAdjust * 0.4)
+            Color6 = New MyColor().FromHSL2(ColorHue, ColorSat, 91 + ColorLightAdjust * 0.1)
+            Color7 = New MyColor().FromHSL2(ColorHue, ColorSat, 95)
+            Color8 = New MyColor().FromHSL2(ColorHue, ColorSat, 97)
+            ColorBg0 = Color4 * 0.4 + Color5 * 0.4 + ColorGray4 * 0.2
+            ColorBg1 = New MyColor(190, Color7)
+
+            ColorSemiTransparent = New MyColor(1, Color8)
+            Application.Current.Resources("ColorBrush1") = New SolidColorBrush(Color1)
+            Application.Current.Resources("ColorBrush2") = New SolidColorBrush(Color2)
+            Application.Current.Resources("ColorBrush3") = New SolidColorBrush(Color3)
+            Application.Current.Resources("ColorBrush4") = New SolidColorBrush(Color4)
+            Application.Current.Resources("ColorBrush5") = New SolidColorBrush(Color5)
+            Application.Current.Resources("ColorBrush6") = New SolidColorBrush(Color6)
+            Application.Current.Resources("ColorBrush7") = New SolidColorBrush(Color7)
+            Application.Current.Resources("ColorBrush8") = New SolidColorBrush(Color8)
+            Application.Current.Resources("ColorBrushBg0") = New SolidColorBrush(ColorBg0)
+            Application.Current.Resources("ColorBrushBg1") = New SolidColorBrush(ColorBg1)
+            Application.Current.Resources("ColorObject1") = CType(Color1, Color)
+            Application.Current.Resources("ColorObject2") = CType(Color2, Color)
+            Application.Current.Resources("ColorObject3") = CType(Color3, Color)
+            Application.Current.Resources("ColorObject4") = CType(Color4, Color)
+            Application.Current.Resources("ColorObject5") = CType(Color5, Color)
+            Application.Current.Resources("ColorObject6") = CType(Color6, Color)
+            Application.Current.Resources("ColorObject7") = CType(Color7, Color)
+            Application.Current.Resources("ColorObject8") = CType(Color8, Color)
+            Application.Current.Resources("ColorObjectBg0") = CType(ColorBg0, Color)
+            Application.Current.Resources("ColorObjectBg1") = CType(ColorBg1, Color)
+            ThemeRefreshMain()
+        Catch ex As Exception
+            Log(ex, "刷新主题颜色失败", LogLevel.Hint)
+        End Try
     End Sub
     End Sub
     Public Sub ThemeRefreshMain()
     Public Sub ThemeRefreshMain()
         RunInUi(
         RunInUi(
         Sub()
         Sub()
-            If Not FrmMain.IsLoaded Then Exit Sub
+            If Not FrmMain.IsLoaded Then Return
             '顶部条背景
             '顶部条背景
             Dim Brush = New LinearGradientBrush With {.EndPoint = New Point(1, 0), .StartPoint = New Point(0, 0)}
             Dim Brush = New LinearGradientBrush With {.EndPoint = New Point(1, 0), .StartPoint = New Point(0, 0)}
-            If ThemeNow = 5 Then
-                Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 25)})
-                Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 15)})
-                Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 25)})
-                FrmMain.PanTitle.Background = Brush
-                FrmMain.PanTitle.Background.Freeze()
-            ElseIf Not (ThemeNow = 12 OrElse ThemeDontClick = 2) Then
-                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 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
             Else
-                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)})
-                FrmMain.PanTitle.Background = Brush
+                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
             End If
+            FrmMain.PanTitle.Background = Brush
+            FrmMain.PanTitle.Background.Freeze()
             '主页面背景
             '主页面背景
             If Setup.Get("UiBackgroundColorful") Then
             If Setup.Get("UiBackgroundColorful") Then
                 Brush = New LinearGradientBrush With {.EndPoint = New Point(0.1, 1), .StartPoint = New Point(0.9, 0)}
                 Brush = New LinearGradientBrush With {.EndPoint = New Point(0.1, 1), .StartPoint = New Point(0.9, 0)}
-                Brush.GradientStops.Add(New GradientStop With {.Offset = -0.1, .Color = New MyColor().FromHSL2(ColorHue - 20, Math.Min(60, ColorSat) * 0.5, 80)})
-                Brush.GradientStops.Add(New GradientStop With {.Offset = 0.4, .Color = New MyColor().FromHSL2(ColorHue, ColorSat * 0.9, 90)})
-                Brush.GradientStops.Add(New GradientStop With {.Offset = 1.1, .Color = New MyColor().FromHSL2(ColorHue + 20, Math.Min(60, ColorSat) * 0.5, 80)})
+                Brush.GradientStops.Add(New GradientStop With {.Offset = -0.1, .Color = New MyColor().FromHSL2(ColorHue - 15, ColorSat * 0.8, 91)})
+                Brush.GradientStops.Add(New GradientStop With {.Offset = 0.4, .Color = New MyColor().FromHSL2(ColorHue, ColorSat * 0.8, 91)})
+                Brush.GradientStops.Add(New GradientStop With {.Offset = 1.1, .Color = New MyColor().FromHSL2(ColorHue + 15, ColorSat * 0.8, 91)})
                 FrmMain.PanForm.Background = Brush
                 FrmMain.PanForm.Background = Brush
             Else
             Else
                 FrmMain.PanForm.Background = New MyColor(245, 245, 245)
                 FrmMain.PanForm.Background = New MyColor(245, 245, 245)

+ 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.9.3.0")>
-<Assembly: AssemblyFileVersion("2.9.3.0")>
+<Assembly: AssemblyVersion("2.10.8.0")>
+<Assembly: AssemblyFileVersion("2.10.8.0")>
 <Assembly: NeutralResourcesLanguage("")>
 <Assembly: NeutralResourcesLanguage("")>

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

@@ -63,9 +63,29 @@ Namespace My.Resources
         '''<summary>
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
         '''</summary>
-        Friend ReadOnly Property Custom() As Byte()
+        Friend ReadOnly Property CacheCow_Client() As Byte()
             Get
             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())
                 Return CType(obj,Byte())
             End Get
             End Get
         End Property
         End Property
@@ -73,9 +93,9 @@ Namespace My.Resources
         '''<summary>
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
         '''</summary>
-        Friend ReadOnly Property Dialogs() As Byte()
+        Friend ReadOnly Property Custom() As Byte()
             Get
             Get
-                Dim obj As Object = ResourceManager.GetObject("Dialogs", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture)
                 Return CType(obj,Byte())
                 Return CType(obj,Byte())
             End Get
             End Get
         End Property
         End Property
@@ -105,7 +125,7 @@ Namespace My.Resources
         '''</summary>
         '''</summary>
         Friend ReadOnly Property Imazen_WebP() As Byte()
         Friend ReadOnly Property Imazen_WebP() As Byte()
             Get
             Get
-                Dim obj As Object = ResourceManager.GetObject("Imazen_WebP", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("Imazen.WebP", resourceCulture)
                 Return CType(obj,Byte())
                 Return CType(obj,Byte())
             End Get
             End Get
         End Property
         End Property
@@ -123,9 +143,9 @@ Namespace My.Resources
         '''<summary>
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
         '''</summary>
-        Friend ReadOnly Property Json() As Byte()
+        Friend ReadOnly Property libwebp64() As Byte()
             Get
             Get
-                Dim obj As Object = ResourceManager.GetObject("Json", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("libwebp64", resourceCulture)
                 Return CType(obj,Byte())
                 Return CType(obj,Byte())
             End Get
             End Get
         End Property
         End Property
@@ -133,9 +153,9 @@ Namespace My.Resources
         '''<summary>
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
         '''</summary>
-        Friend ReadOnly Property libwebp64() As Byte()
+        Friend ReadOnly Property ModData() As Byte()
             Get
             Get
-                Dim obj As Object = ResourceManager.GetObject("libwebp64", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("ModData", resourceCulture)
                 Return CType(obj,Byte())
                 Return CType(obj,Byte())
             End Get
             End Get
         End Property
         End Property
@@ -143,9 +163,9 @@ Namespace My.Resources
         '''<summary>
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
         '''</summary>
-        Friend ReadOnly Property ModData() As Byte()
+        Friend ReadOnly Property NAudio() As Byte()
             Get
             Get
-                Dim obj As Object = ResourceManager.GetObject("ModData", resourceCulture)
+                Dim obj As Object = ResourceManager.GetObject("NAudio", resourceCulture)
                 Return CType(obj,Byte())
                 Return CType(obj,Byte())
             End Get
             End Get
         End Property
         End Property
@@ -153,9 +173,29 @@ Namespace My.Resources
         '''<summary>
         '''<summary>
         '''  查找 System.Byte[] 类型的本地化资源。
         '''  查找 System.Byte[] 类型的本地化资源。
         '''</summary>
         '''</summary>
-        Friend ReadOnly Property NAudio() As Byte()
+        Friend ReadOnly Property Newtonsoft_Json() As Byte()
             Get
             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())
                 Return CType(obj,Byte())
             End Get
             End Get
         End Property
         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>
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   </resheader>
   <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
   <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">
   <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>
     <value>..\resources\custom.xaml;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   </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">
   <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>
     <value>..\Resources\forge-installer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   </data>
   <data name="Help" type="System.Resources.ResXFileRef, System.Windows.Forms">
   <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>
     <value>..\Resources\Help.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   </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>
     <value>..\Resources\Imazen.WebP.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   </data>
   <data name="JavaWrapper" type="System.Resources.ResXFileRef, System.Windows.Forms">
   <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>
     <value>..\Resources\java-wrapper.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   </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">
   <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>
     <value>..\Resources\libwebp64.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   </data>
@@ -148,6 +151,15 @@
   <data name="NAudio" type="System.Resources.ResXFileRef, System.Windows.Forms">
   <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>
     <value>..\Resources\NAudio.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   </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">
   <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>
     <value>..\Resources\transformer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   </data>

+ 0 - 0
Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml → Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml


+ 35 - 24
Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb → Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb

@@ -19,7 +19,7 @@
             Return LabTitle.Text
             Return LabTitle.Text
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If LabTitle.Text = value Then Exit Property
+            If LabTitle.Text = value Then Return
             LabTitle.Text = value
             LabTitle.Text = value
         End Set
         End Set
     End Property
     End Property
@@ -30,7 +30,7 @@
             Return If(LabTitleRaw?.Text, "")
             Return If(LabTitleRaw?.Text, "")
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If LabTitleRaw.Text = value Then Exit Property
+            If LabTitleRaw.Text = value Then Return
             LabTitleRaw.Text = value
             LabTitleRaw.Text = value
             LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
             LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
         End Set
         End Set
@@ -42,7 +42,7 @@
             Return LabInfo.Text
             Return LabInfo.Text
         End Get
         End Get
         Set(value As String)
         Set(value As String)
-            If LabInfo.Text = value Then Exit Property
+            If LabInfo.Text = value Then Return
             LabInfo.Text = value
             LabInfo.Text = value
         End Set
         End Set
     End Property
     End Property
@@ -88,41 +88,52 @@
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
     Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
         If IsMouseDown Then
         If IsMouseDown Then
             RaiseEvent Click(sender, e)
             RaiseEvent Click(sender, e)
-            If e.Handled Then Exit Sub
+            If e.Handled Then Return
             Log("[Control] 按下资源工程列表项:" & LabTitle.Text)
             Log("[Control] 按下资源工程列表项:" & LabTitle.Text)
         End If
         End If
     End Sub
     End Sub
-    Private Sub ProjectClick(sender As MyCompItem, e As EventArgs) Handles Me.Click
+    Private Sub MyCompItem_Click(sender As MyCompItem, e As EventArgs) Handles Me.Click
         '记录当前展开的卡片标题(#2712)
         '记录当前展开的卡片标题(#2712)
         Dim Titles As New List(Of String)
         Dim Titles As New List(Of String)
         If FrmMain.PageCurrent.Page = FormMain.PageType.CompDetail Then
         If FrmMain.PageCurrent.Page = FormMain.PageType.CompDetail Then
             For Each Card As MyCard In FrmDownloadCompDetail.PanResults.Children
             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
             Next
             Log("[Comp] 记录当前已展开的卡片:" & String.Join("、", Titles))
             Log("[Comp] 记录当前已展开的卡片:" & String.Join("、", Titles))
             FrmMain.PageCurrent.Additional(1) = Titles
             FrmMain.PageCurrent.Additional(1) = Titles
         End If
         End If
         '打开详情页
         '打开详情页
-        Dim TargetVersion As String
-        Dim TargetLoader As CompModLoaderType
-        If FrmMain.PageCurrent.Page = FormMain.PageType.CompDetail Then
+        Dim TargetType As CompType
+        Dim TargetVersion As String = Nothing
+        Dim TargetLoader As CompModLoaderType = CompModLoaderType.Any
+        If FrmMain.PageCurrent.Page = FormMain.PageType.Download Then
+            '从下载页进入
+            Select Case FrmMain.PageCurrentSub
+                Case FormMain.PageSubType.DownloadMod
+                    TargetType = CompType.Mod
+                    TargetVersion = FrmDownloadMod.Content.Loader.Input.GameVersion
+                    TargetLoader = FrmDownloadMod.Content.Loader.Input.ModLoader
+                Case FormMain.PageSubType.DownloadPack
+                    TargetType = CompType.ModPack
+                    TargetVersion = FrmDownloadPack.Content.Loader.Input.GameVersion
+                Case FormMain.PageSubType.DownloadDataPack
+                    TargetType = CompType.DataPack
+                    TargetVersion = FrmDownloadDataPack.Content.Loader.Input.GameVersion
+                Case FormMain.PageSubType.DownloadResourcePack
+                    TargetType = CompType.ResourcePack
+                    TargetVersion = FrmDownloadResourcePack.Content.Loader.Input.GameVersion
+                Case FormMain.PageSubType.DownloadShader
+                    TargetType = CompType.Shader
+                    TargetVersion = FrmDownloadShader.Content.Loader.Input.GameVersion
+            End Select
+        Else
+            '从详情页进入(查看前置)
+            TargetType = CompType.Any '允许任意类别
             TargetVersion = FrmMain.PageCurrent.Additional(2)
             TargetVersion = FrmMain.PageCurrent.Additional(2)
             TargetLoader = FrmMain.PageCurrent.Additional(3)
             TargetLoader = FrmMain.PageCurrent.Additional(3)
-        Else
-            Select Case CType(sender.Tag, CompProject).Type
-                Case CompType.Mod
-                    TargetVersion = If(PageDownloadMod.Loader.Input.GameVersion, "")
-                    TargetLoader = PageDownloadMod.Loader.Input.ModLoader
-                Case CompType.ModPack
-                    TargetVersion = If(PageDownloadPack.Loader.Input.GameVersion, "")
-                Case Else 'CompType.ResourcePack
-                    'FUTURE: Res
-                    TargetVersion = "" 'If(PageDownloadResource.Loader.Input.GameVersion, "")
-            End Select
         End If
         End If
-        If CType(sender.Tag, CompProject).Type <> CompType.Mod Then TargetLoader = CompModLoaderType.Any
         FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail,
         FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail,
-                           .Additional = {sender.Tag, New List(Of String), TargetVersion, TargetLoader}})
+                           .Additional = {sender.Tag, New List(Of String), TargetVersion, TargetLoader, TargetType}})
     End Sub
     End Sub
 
 
     '鼠标点击判定
     '鼠标点击判定
@@ -174,7 +185,7 @@
     ''' </summary>
     ''' </summary>
     Public Property CanInteraction As Boolean = True
     Public Property CanInteraction As Boolean = True
     Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
     Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
-        If Not CanInteraction Then Exit Sub
+        If Not CanInteraction Then Return
         '判断当前颜色
         '判断当前颜色
         Dim StateNew As String, Time As Integer
         Dim StateNew As String, Time As Integer
         If IsMouseOver Then
         If IsMouseOver Then
@@ -189,7 +200,7 @@
             StateNew = "Idle"
             StateNew = "Idle"
             Time = 180
             Time = 180
         End If
         End If
-        If StateLast = StateNew Then Exit Sub
+        If StateLast = StateNew Then Return
         StateLast = StateNew
         StateLast = StateNew
         '触发颜色动画
         '触发颜色动画
         If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
         If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画

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

@@ -0,0 +1,95 @@
+<local:MyScrollViewer x:Class="PageComp"
+      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+      xmlns:local="clr-namespace:PCL"
+      mc:Ignorable="d" 
+      VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
+    <StackPanel Orientation="Vertical" Margin="25">
+        <local:MyCard Title="搜索XXX" Margin="0,0,0,15" x:Name="PanAlways">
+            <StackPanel Margin="25,40,25,15">
+                <Grid Margin="0,0,0,11" HorizontalAlignment="Stretch">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition Width="1.3*" />
+                        <ColumnDefinition Width="1.2*" />
+                        <ColumnDefinition Width="50" />
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition Width="1*" />
+                    </Grid.ColumnDefinitions>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="28" />
+                        <RowDefinition Height="9" />
+                        <RowDefinition Height="28" />
+                    </Grid.RowDefinitions>
+                    <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="名称" Margin="0,0,18,0" />
+                    <local:MyTextBox x:Name="TextSearchName" Grid.Column="1" Grid.ColumnSpan="2" />
+                    <TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Left" Text="来源" Margin="0,0,18,0" />
+                    <local:MyComboBox x:Name="ComboSearchSource" Grid.Column="5">
+                        <local:MyComboBoxItem Content="全部" Tag="3" IsSelected="True" />
+                        <local:MyComboBoxItem Content="CurseForge" Tag="1" />
+                        <local:MyComboBoxItem Content="Modrinth" Tag="2" />
+                    </local:MyComboBox>
+                    <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.8" />
+                        <local:MyComboBoxItem Content="1.21.1" />
+                        <local:MyComboBoxItem Content="1.20.6" />
+                        <local:MyComboBoxItem Content="1.20.1" />
+                        <local:MyComboBoxItem Content="1.19.4" />
+                        <local:MyComboBoxItem Content="1.19.2" />
+                        <local:MyComboBoxItem Content="1.18.2" />
+                        <local:MyComboBoxItem Content="1.16.5" />
+                        <local:MyComboBoxItem Content="1.12.2" />
+                        <local:MyComboBoxItem Content="1.10.2" />
+                        <local:MyComboBoxItem Content="1.8.9" />
+                        <local:MyComboBoxItem Content="1.7.10" />
+                    </local:MyComboBox>
+                    <local:MyComboBox x:Name="ComboSearchLoader" Visibility="Collapsed" Grid.Row="2" Grid.Column="2" MaxDropDownHeight="320" Margin="10,0,0,0">
+                        <local:MyComboBoxItem Content="任意 Mod 加载器" IsSelected="True" Tag="0" />
+                        <local:MyComboBoxItem Content="Forge" Tag="1" />
+                        <local:MyComboBoxItem Content="NeoForge" Tag="6" />
+                        <local:MyComboBoxItem Content="Fabric" Tag="4" />
+                        <local:MyComboBoxItem Content="Quilt" Tag="5" />
+                    </local:MyComboBox>
+                    <TextBlock VerticalAlignment="Center" Grid.Column="4" Grid.Row="2" HorizontalAlignment="Left" Text="类型" Margin="0,0,18,0" />
+                    <local:MyComboBox x:Name="ComboSearchTag" Grid.Column="5" Grid.Row="2" MaxDropDownHeight="320" />
+                </Grid>
+                <Grid Margin="0,4,0,6" Height="35">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                        <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                        <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
+                    </Grid.ColumnDefinitions>
+                    <local:MyButton x:Name="BtnSearchRun" Text="搜索" MinWidth="140" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" />
+                    <local:MyButton x:Name="BtnSearchReset" Text="重置条件" MinWidth="140" Padding="13,0" Margin="0,0,20,0" Grid.Column="1" />
+                    <local:MyButton x:Name="BtnSearchInstallModPack" Text="安装已有整合包" MinWidth="140" Padding="13,0" Margin="0,0,20,0" Grid.Column="2"
+                                    ToolTip="在当前选择的 Minecraft 文件夹下安装整合包" ToolTipService.HorizontalOffset="-55" ToolTipService.VerticalOffset="5" />
+                </Grid>
+            </StackPanel>
+        </local:MyCard>
+        <StackPanel x:Name="PanContent">
+            <local:MyHint x:Name="HintError" HasBorder="True" Text="无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。&#xa;请稍后再试,或使用 VPN 改善网络环境。" Theme="Yellow" Margin="0,0,0,15" />
+            <local:MyCard x:Name="CardProjects" UseAnimation="False">
+                <StackPanel x:Name="PanProjects" Margin="12" />
+            </local:MyCard>
+            <local:MyCard Margin="0,15,0,0" x:Name="CardPages" HorizontalAlignment="Center">
+                <StackPanel Orientation="Horizontal" Height="23" Margin="10,7">
+                    <local:MyIconButton x:Name="BtnPageFirst" IsEnabled="False" Opacity="0.2" Margin="0,0.5,5,0.5"
+                        Logo="M496.213333 329.856L315.306667 510.848l180.992 181.077333a42.666667 42.666667 0 1 1-60.330667 60.330667l-211.2-211.2a42.453333 42.453333 0 0 1-11.818667-22.613333l-0.597333-5.034667v-5.034667a42.496 42.496 0 0 1 12.373333-27.648l211.2-211.2a42.666667 42.666667 0 0 1 60.373334 60.330667z m298.666667 0l-180.949333 180.992 180.992 181.077333a42.666667 42.666667 0 1 1-60.330667 60.330667l-211.2-211.2a42.453333 42.453333 0 0 1-11.818667-22.613333l-0.597333-5.034667v-5.034667a42.496 42.496 0 0 1 12.373333-27.648l211.2-211.2a42.666667 42.666667 0 0 1 60.373334 60.330667z" />
+                    <local:MyIconButton x:Name="BtnPageLeft" IsEnabled="False" Opacity="0.2" Margin="0,0,5,0"
+                        Logo="M650.752 278.357333l-241.322667 241.365334 241.322667 241.365333a42.666667 42.666667 0 1 1-60.330667 60.330667l-271.530666-271.530667a42.453333 42.453333 0 0 1-11.818667-22.613333l-0.597333-5.034667v-5.034667a42.496 42.496 0 0 1 12.416-27.648l271.530666-271.530666a42.666667 42.666667 0 0 1 60.330667 60.330666z" />
+                    <TextBlock x:Name="LabPage" Text="1" 
+                               Margin="8,0,13,0" FontSize="15" VerticalAlignment="Center" Foreground="{DynamicResource ColorBrush3}" />
+                    <local:MyIconButton x:Name="BtnPageRight" Margin="0,0,30,0"
+                        Logo="M404.309333 278.357333l241.322667 241.365334-241.322667 241.365333a42.666667 42.666667 0 1 0 60.330667 60.330667l271.530667-271.530667a42.453333 42.453333 0 0 0 11.818666-22.613333l0.597334-5.034667v-5.034667a42.496 42.496 0 0 0-12.416-27.648L464.64 218.026667a42.666667 42.666667 0 0 0-60.330667 60.330666z" />
+                </StackPanel>
+            </local:MyCard>
+        </StackPanel>
+        <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
+            <local:MyLoading Text="正在获取XXX列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
+        </local:MyCard>
+    </StackPanel>
+</local:MyScrollViewer>

+ 115 - 37
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb → Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb

@@ -1,17 +1,83 @@
-Public Class PageDownloadMod
+Imports System.Windows.Markup
+
+<ContentProperty("SearchTags")>
+Public Class PageComp
+
+#Region "属性"
+
+    ''' <summary>
+    ''' 用于 XAML 快速设置的 Tag 下拉框列表。
+    ''' </summary>
+    Public ReadOnly Property SearchTags As ItemCollection
+        Get
+            Return ComboSearchTag.Items
+        End Get
+    End Property
+
+    ''' <summary>
+    ''' 英文前后不含空格的可读资源类型名,例如 "Mod"、"整合包"。
+    ''' </summary>
+    Public Property TypeName As String
+        Get
+            Return _TypeName
+        End Get
+        Set(Value As String)
+            If _TypeName = Value Then Return
+            _TypeName = Value
+            Loader.Name = $"社区资源获取:{Value}"
+        End Set
+    End Property
+    Private _TypeName As String = ""
 
 
-    Public Const PageSize = 40
     ''' <summary>
     ''' <summary>
-    ''' 在切换到该页面时自动设置的目标版本。
+    ''' 英文前后含一个空格的可读资源类型名,例如 " Mod "、"整合包"。
+    ''' </summary>
+    Public Property TypeNameSpaced As String
+        Get
+            Return _TypeNameSpaced
+        End Get
+        Set(Value As String)
+            If _TypeNameSpaced = Value Then Return
+            _TypeNameSpaced = Value
+            PanAlways.Title = $"搜索{Value}"
+            Load.Text = $"正在获取{Value}列表"
+        End Set
+    End Property
+    Private _TypeNameSpaced As String = ""
+
+    ''' <summary>
+    ''' 该页面对应的资源类型。
+    ''' </summary>
+    Public Property PageType As CompType
+        Get
+            Return _Type
+        End Get
+        Set(Value As CompType)
+            If _Type = Value Then Return
+            _Type = Value
+            BtnSearchInstallModPack.Visibility = If(Value = CompType.ModPack, Visibility.Visible, Visibility.Collapsed)
+        End Set
+    End Property
+    Private _Type As CompType = -1
+
+#End Region
+
+#Region "加载"
+
+    ''' <summary>
+    ''' 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。
     ''' </summary>
     ''' </summary>
     Public Shared TargetVersion As McVersion = Nothing
     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}
 
 
-    '加载器信息
-    Public Shared Loader As New LoaderTask(Of CompProjectRequest, Integer)("CompProject Mod", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000}
-    Public Shared Storage As New CompProjectStorage
-    Public Shared Page As Integer = 0
     Private IsLoaderInited As Boolean = False
     Private IsLoaderInited As Boolean = False
-    Private Sub PageDownloadMod_Inited(sender As Object, e As EventArgs) Handles Me.Loaded
+    Private Sub PageCompControls_Inited(sender As Object, e As EventArgs) Handles Me.Loaded
         '不知道从 Initialized 改成 Loaded 会不会有问题,但用 Initialized 会导致初始的筛选器修改被覆盖回默认值
         '不知道从 Initialized 改成 Loaded 会不会有问题,但用 Initialized 会导致初始的筛选器修改被覆盖回默认值
         If TargetVersion IsNot Nothing Then
         If TargetVersion IsNot Nothing Then
             '设置目标
             '设置目标
@@ -32,45 +98,53 @@
                 ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge")
                 ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge")
             End If
             End If
             TargetVersion = Nothing
             TargetVersion = Nothing
+            If TargetName IsNot Nothing Then
+                TextSearchName.Text = TargetName
+                TargetName = Nothing
+            End If
             '如果已经完成请求,则重新开始
             '如果已经完成请求,则重新开始
             If IsLoaderInited Then StartNewSearch()
             If IsLoaderInited Then StartNewSearch()
-            PanScroll.ScrollToHome()
+            ScrollToHome()
         End If
         End If
         '加载器初始化
         '加载器初始化
         If IsLoaderInited Then Return
         If IsLoaderInited Then Return
         IsLoaderInited = True
         IsLoaderInited = True
-        PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput)
+        CType(Parent, MyPageRight).PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput)
         If McVersionHighest = -1 Then McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1)))
         If McVersionHighest = -1 Then McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1)))
     End Sub
     End Sub
-    Private Shared Function LoaderInput() As CompProjectRequest
-        Dim Request As New CompProjectRequest(CompType.Mod, Storage, (Page + 1) * PageSize)
-        If FrmDownloadMod IsNot Nothing Then
-            Dim ModLoader As CompModLoaderType = Val(FrmDownloadMod.ComboSearchLoader.SelectedItem.Tag)
-            Dim GameVersion As String = If(FrmDownloadMod.TextSearchVersion.Text = "全部 (也可自行输入)", Nothing,
-                    If(FrmDownloadMod.TextSearchVersion.Text.Contains(".") OrElse FrmDownloadMod.TextSearchVersion.Text.Contains("w"), FrmDownloadMod.TextSearchVersion.Text, Nothing))
-            If GameVersion IsNot Nothing AndAlso GameVersion.Contains(".") AndAlso Val(GameVersion.Split(".")(1)) < 14 AndAlso '1.14-
-                ModLoader = CompModLoaderType.Forge Then '选择了 Forge
-                ModLoader = CompModLoaderType.Any '此时,视作没有筛选 Mod Loader(因为部分老 Mod 没有设置自己支持的加载器)
-            End If
-            With Request
-                .SearchText = FrmDownloadMod.TextSearchName.Text
-                .GameVersion = GameVersion
-                .Tag = FrmDownloadMod.ComboSearchTag.SelectedItem.Tag
-                .ModLoader = ModLoader
-                .Source = CType(Val(FrmDownloadMod.ComboSearchSource.SelectedItem.Tag), CompSourceType)
-            End With
-        End If
+    Private Function LoaderInput() As CompProjectRequest
+        Dim Request As New CompProjectRequest(PageType, Storage, (Page + 1) * PageSize)
+        Dim GameVersion As String = If(TextSearchVersion.Text = "全部 (也可自行输入)", Nothing,
+                If(TextSearchVersion.Text.Contains(".") OrElse TextSearchVersion.Text.Contains("w"), TextSearchVersion.Text, Nothing))
+        With Request
+            .SearchText = TextSearchName.Text
+            .GameVersion = GameVersion
+            .Tag = ComboSearchTag.SelectedItem.Tag
+            .ModLoader = If(PageType = CompType.Mod, Val(ComboSearchLoader.SelectedItem.Tag), CompModLoaderType.Any)
+            .Source = CType(Val(ComboSearchSource.SelectedItem.Tag), CompSourceType)
+        End With
         Return Request
         Return Request
     End Function
     End Function
 
 
+#End Region
+
+    Public Storage As New CompProjectStorage
+    ''' <summary>
+    ''' 每页展示的结果数量。
+    ''' </summary>
+    Public Const PageSize = 40
+    Public Page As Integer = 0
+
     '结果 UI 化
     '结果 UI 化
     Private Sub Load_OnFinish()
     Private Sub Load_OnFinish()
         Try
         Try
-            Log($"[Comp] 开始可视化 Mod 列表,已储藏 {Storage.Results.Count} 个结果,当前在第 {Page + 1} 页")
+            Log($"[Comp] 开始可视化{TypeNameSpaced}列表,已储藏 {Storage.Results.Count} 个结果,当前在第 {Page + 1} 页")
             '列表项
             '列表项
             PanProjects.Children.Clear()
             PanProjects.Children.Clear()
             For i = Math.Min(Page * PageSize, Storage.Results.Count - 1) To Math.Min((Page + 1) * PageSize - 1, Storage.Results.Count - 1)
             For i = Math.Min(Page * PageSize, Storage.Results.Count - 1) To Math.Min((Page + 1) * PageSize - 1, Storage.Results.Count - 1)
-                PanProjects.Children.Add(Storage.Results(i).ToCompItem(Loader.Input.GameVersion Is Nothing, Loader.Input.ModLoader = CompModLoaderType.Any))
+                PanProjects.Children.Add(Storage.Results(i).ToCompItem(
+                    ShowMcVersionDesc:=Loader.Input.GameVersion Is Nothing,
+                    ShowLoaderDesc:=Loader.Input.ModLoader = CompModLoaderType.Any AndAlso (PageType = CompType.Mod OrElse PageType = CompType.ModPack)))
             Next
             Next
             '页码
             '页码
             CardPages.Visibility = If(Storage.Results.Count > 40 OrElse
             CardPages.Visibility = If(Storage.Results.Count > 40 OrElse
@@ -94,9 +168,9 @@
                 HintError.Text = Storage.ErrorMessage
                 HintError.Text = Storage.ErrorMessage
             End If
             End If
             '强制返回顶部
             '强制返回顶部
-            PanBack.ScrollToTop()
+            ScrollToTop()
         Catch ex As Exception
         Catch ex As Exception
-            Log(ex, "可视化 Mod 列表出错", LogLevel.Feedback)
+            Log(ex, $"可视化{TypeNameSpaced}列表出错", LogLevel.Feedback)
         End Try
         End Try
     End Sub
     End Sub
 
 
@@ -107,14 +181,13 @@
                 Dim ErrorMessage As String = ""
                 Dim ErrorMessage As String = ""
                 If Loader.Error IsNot Nothing Then ErrorMessage = Loader.Error.Message
                 If Loader.Error IsNot Nothing Then ErrorMessage = Loader.Error.Message
                 If ErrorMessage.Contains("不是有效的 json 文件") Then
                 If ErrorMessage.Contains("不是有效的 json 文件") Then
-                    Log("[Download] 下载的 Mod 列表 json 文件损坏,已自动重试", LogLevel.Debug)
-                    PageLoaderRestart()
+                    Log($"[Download] 下载的{TypeNameSpaced}列表 json 文件损坏,已自动重试", LogLevel.Debug)
+                    CType(Parent, MyPageRight).PageLoaderRestart()
                 End If
                 End If
         End Select
         End Select
     End Sub
     End Sub
 
 
     '切换页码
     '切换页码
-
     Private Sub BtnPageFirst_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageFirst.Click
     Private Sub BtnPageFirst_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageFirst.Click
         ChangePage(0)
         ChangePage(0)
     End Sub
     End Sub
@@ -128,7 +201,7 @@
         CardPages.IsEnabled = False
         CardPages.IsEnabled = False
         Page = NewPage
         Page = NewPage
         FrmMain.BackToTop()
         FrmMain.BackToTop()
-        Log($"[Download] Mod 切换到第 {Page + 1} 页")
+        Log($"[Download] {TypeName}:切换到第 {Page + 1} 页")
         RunInThread(
         RunInThread(
         Sub()
         Sub()
             Thread.Sleep(100) '等待向上滚的动画结束
             Thread.Sleep(100) '等待向上滚的动画结束
@@ -166,7 +239,7 @@
         If Not TextSearchVersion.IsDropDownOpen Then UpdateSearchLoaderVisibility()
         If Not TextSearchVersion.IsDropDownOpen Then UpdateSearchLoaderVisibility()
     End Sub
     End Sub
     Private Sub UpdateSearchLoaderVisibility() Handles TextSearchVersion.DropDownClosed
     Private Sub UpdateSearchLoaderVisibility() Handles TextSearchVersion.DropDownClosed
-        If TextSearchVersion.Text.Contains(".") OrElse TextSearchVersion.Text.Contains("w") Then
+        If PageType = CompType.Mod AndAlso (TextSearchVersion.Text.Contains(".") OrElse TextSearchVersion.Text.Contains("w")) Then
             ComboSearchLoader.Visibility = Visibility.Visible
             ComboSearchLoader.Visibility = Visibility.Visible
             Grid.SetColumnSpan(TextSearchVersion, 1)
             Grid.SetColumnSpan(TextSearchVersion, 1)
         Else
         Else
@@ -178,4 +251,9 @@
 
 
 #End Region
 #End Region
 
 
+    '安装已有整合包按钮
+    Private Sub BtnSearchInstallModPack_Click(sender As Object, e As EventArgs) Handles BtnSearchInstallModPack.Click
+        ModpackInstall()
+    End Sub
+
 End Class
 End Class

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

@@ -3,7 +3,7 @@
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     mc:Ignorable="d" x:Class="PageDownloadCompDetail"
     mc:Ignorable="d" x:Class="PageDownloadCompDetail"
-    PanScroll="{Binding ElementName=PanBack}">
+    PanScroll="PanBack">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
     <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
         <Grid Margin="25,25,25,10">
         <Grid Margin="25,25,25,10">
             <Grid.RowDefinitions>
             <Grid.RowDefinitions>

+ 113 - 76
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb → Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb

@@ -4,23 +4,31 @@
 #Region "加载器"
 #Region "加载器"
 
 
     Private CompFileLoader As New LoaderTask(Of Integer, List(Of CompFile))(
     Private CompFileLoader As New LoaderTask(Of Integer, List(Of CompFile))(
-        "Comp File", Sub(Task As LoaderTask(Of Integer, List(Of CompFile))) Task.Output = CompFilesGet(Project.Id, Project.FromCurseForge))
+        "Comp File",
+        Sub(Task)
+            LoadTargetFromAdditional()
+            Dim Result = CompFilesGet(Project.Id, Project.FromCurseForge)
+            If Task.IsAborted Then Return
+            Task.Output = Result
+        End Sub)
 
 
     '初始化加载器信息
     '初始化加载器信息
     Private Sub PageDownloadCompDetail_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
     Private Sub PageDownloadCompDetail_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
-        Project = FrmMain.PageCurrent.Additional(0)
-        TargetVersion = FrmMain.PageCurrent.Additional(2)
-        TargetLoader = FrmMain.PageCurrent.Additional(3)
+        LoadTargetFromAdditional()
         PageLoaderInit(Load, PanLoad, PanMain, CardIntro, CompFileLoader, AddressOf Load_OnFinish)
         PageLoaderInit(Load, PanLoad, PanMain, CardIntro, CompFileLoader, AddressOf Load_OnFinish)
     End Sub
     End Sub
-    Private Sub PageDownloadCompDetail_Loaded(sender As Object, e As EventArgs) Handles Me.Loaded
-        'Initialized 只会执行一次
+    Public Sub LoadTargetFromAdditional() Handles Me.Loaded
         Project = FrmMain.PageCurrent.Additional(0)
         Project = FrmMain.PageCurrent.Additional(0)
         TargetVersion = FrmMain.PageCurrent.Additional(2)
         TargetVersion = FrmMain.PageCurrent.Additional(2)
         TargetLoader = FrmMain.PageCurrent.Additional(3)
         TargetLoader = FrmMain.PageCurrent.Additional(3)
+        PageType = FrmMain.PageCurrent.Additional(4)
     End Sub
     End Sub
     Private Project As CompProject
     Private Project As CompProject
     Private TargetVersion As String, TargetLoader As CompModLoaderType
     Private TargetVersion As String, TargetLoader As CompModLoaderType
+    ''' <summary>
+    ''' 当前页面应展示的内容类别。可能为 Any。
+    ''' </summary>
+    Private PageType As CompType
     '自动重试
     '自动重试
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
         Select Case CompFileLoader.State
         Select Case CompFileLoader.State
@@ -62,18 +70,33 @@
 
 
     Private VersionFilter As String
     Private VersionFilter As String
     Private IsMajorVersionFilter As Boolean '是否按大版本号筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...)
     Private IsMajorVersionFilter As Boolean '是否按大版本号筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...)
+    '筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表)
+    Private Function GetResults() As List(Of CompFile)
+        Dim Results As List(Of CompFile) = CompFileLoader.Output
+        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()
     Private Sub Load_OnFinish()
+        Dim Results = GetResults()
+
         '初始化筛选器
         '初始化筛选器
         Dim VersionFilters As List(Of String)
         Dim VersionFilters As List(Of String)
 
 
         '按小版本号筛选?
         '按小版本号筛选?
         IsMajorVersionFilter = False
         IsMajorVersionFilter = False
-        VersionFilters = CompFileLoader.Output.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)).
+        VersionFilters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)).
             Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList
             Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList
         '按大版本号筛选?
         '按大版本号筛选?
         If VersionFilters.Count >= 9 Then
         If VersionFilters.Count >= 9 Then
             IsMajorVersionFilter = True
             IsMajorVersionFilter = True
-            VersionFilters = CompFileLoader.Output.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)).
+            VersionFilters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)).
                 Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList
                 Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList
         End If
         End If
 
 
@@ -100,7 +123,7 @@
             '自动选择
             '自动选择
             Dim ToCheck As MyRadioButton = Nothing
             Dim ToCheck As MyRadioButton = Nothing
             If TargetVersion <> "" Then
             If TargetVersion <> "" Then
-                Dim TargetFile = CompFileLoader.Output.FirstOrDefault(Function(v) v.GameVersions.Contains(TargetVersion))
+                Dim TargetFile = Results.FirstOrDefault(Function(v) v.GameVersions.Contains(TargetVersion))
                 If TargetFile IsNot Nothing Then
                 If TargetFile IsNot Nothing Then
                     Dim TargetGroup = GetGroupedVersionName(TargetVersion, IsMajorVersionFilter, True)
                     Dim TargetGroup = GetGroupedVersionName(TargetVersion, IsMajorVersionFilter, True)
                     For Each Button As MyRadioButton In PanFilter.Children
                     For Each Button As MyRadioButton In PanFilter.Children
@@ -118,24 +141,26 @@
         UpdateFilterResult()
         UpdateFilterResult()
     End Sub
     End Sub
     Private Sub UpdateFilterResult()
     Private Sub UpdateFilterResult()
+        Dim Results = GetResults()
+
         Dim TargetCardName As String = If(TargetVersion <> "" OrElse TargetLoader <> CompModLoaderType.Any,
         Dim TargetCardName As String = If(TargetVersion <> "" OrElse TargetLoader <> CompModLoaderType.Any,
             $"所选版本:{If(TargetLoader <> CompModLoaderType.Any, TargetLoader.ToString & " ", "")}{TargetVersion}", "")
             $"所选版本:{If(TargetLoader <> CompModLoaderType.Any, TargetLoader.ToString & " ", "")}{TargetVersion}", "")
         '归类到卡片下
         '归类到卡片下
         Dim Dict As New SortedDictionary(Of String, List(Of CompFile))(New CardSorter(TargetCardName))
         Dim Dict As New SortedDictionary(Of String, List(Of CompFile))(New CardSorter(TargetCardName))
         Dict.Add("其他版本", New List(Of CompFile))
         Dict.Add("其他版本", New List(Of CompFile))
         Dim SupportedLoaders As New List(Of Integer)([Enum].GetValues(GetType(CompModLoaderType)))
         Dim SupportedLoaders As New List(Of Integer)([Enum].GetValues(GetType(CompModLoaderType)))
-        For Each Version As CompFile In CompFileLoader.Output
+        For Each Version As CompFile In Results
             For Each GameVersion In Version.GameVersions
             For Each GameVersion In Version.GameVersions
                 '检查是否符合版本筛选器
                 '检查是否符合版本筛选器
                 If VersionFilter IsNot Nothing AndAlso
                 If VersionFilter IsNot Nothing AndAlso
                     GetGroupedVersionName(GameVersion, IsMajorVersionFilter, True) <> VersionFilter Then Continue For
                     GetGroupedVersionName(GameVersion, IsMajorVersionFilter, True) <> VersionFilter Then Continue For
                 '决定添加到哪个卡片
                 '决定添加到哪个卡片
-                Dim Ver As String = GetGroupedVersionName(GameVersion, False, False)
+                Dim VerName As String = GetGroupedVersionName(GameVersion, False, False)
                 '遍历加入的加载器列表
                 '遍历加入的加载器列表
                 Dim Loaders As New List(Of String)
                 Dim Loaders As New List(Of String)
-                If Project.ModLoaders.Count > 1 AndAlso '至少有两个加载器
-                    Project.Type = CompType.Mod AndAlso '是 Mod
-                    Ver.StartsWith("1.") Then '不是 “快照版本” 之类的
+                If Project.ModLoaders.Count > 1 AndAlso '工程至少有两个加载器
+                    Version.Type = CompType.Mod AndAlso '是 Mod
+                    VerName.StartsWith("1.") Then '不是 “快照版本” 之类的
                     For Each Loader In Version.ModLoaders
                     For Each Loader In Version.ModLoaders
                         If Loader = CompModLoaderType.Quilt AndAlso Setup.Get("ToolDownloadIgnoreQuilt") Then Continue For
                         If Loader = CompModLoaderType.Quilt AndAlso Setup.Get("ToolDownloadIgnoreQuilt") Then Continue For
                         If SupportedLoaders.Contains(Loader) Then Loaders.Add(Loader.ToString & " ")
                         If SupportedLoaders.Contains(Loader) Then Loaders.Add(Loader.ToString & " ")
@@ -144,16 +169,16 @@
                 If Not Loaders.Any() Then Loaders.Add("") '保底加一个空的,确保它在一张卡片里
                 If Not Loaders.Any() Then Loaders.Add("") '保底加一个空的,确保它在一张卡片里
                 '实际添加
                 '实际添加
                 For Each Loader In Loaders
                 For Each Loader In Loaders
-                    Dim TargetCard As String = Loader & Ver
+                    Dim TargetCard As String = Loader & VerName
                     If Not Dict.ContainsKey(TargetCard) Then Dict.Add(TargetCard, New List(Of CompFile))
                     If Not Dict.ContainsKey(TargetCard) Then Dict.Add(TargetCard, New List(Of CompFile))
                     If Not Dict(TargetCard).Contains(Version) Then Dict(TargetCard).Add(Version)
                     If Not Dict(TargetCard).Contains(Version) Then Dict(TargetCard).Add(Version)
                 Next
                 Next
             Next
             Next
         Next
         Next
         '添加筛选的版本的卡片
         '添加筛选的版本的卡片
-        If TargetCardName <> "" Then
+        If TargetCardName <> "" AndAlso (VersionFilter Is Nothing OrElse GetGroupedVersionName(TargetVersion, IsMajorVersionFilter, True).StartsWithF(VersionFilter)) Then
             Dict.Add(TargetCardName, New List(Of CompFile))
             Dict.Add(TargetCardName, New List(Of CompFile))
-            For Each Version As CompFile In CompFileLoader.Output
+            For Each Version As CompFile In Results
                 If Version.GameVersions.Contains(TargetVersion) AndAlso
                 If Version.GameVersions.Contains(TargetVersion) AndAlso
                    (TargetLoader = CompModLoaderType.Any OrElse Version.ModLoaders.Contains(TargetLoader)) Then
                    (TargetLoader = CompModLoaderType.Any OrElse Version.ModLoaders.Contains(TargetLoader)) Then
                     '检查是否符合版本筛选器
                     '检查是否符合版本筛选器
@@ -169,7 +194,7 @@
             For Each Pair As KeyValuePair(Of String, List(Of CompFile)) In Dict
             For Each Pair As KeyValuePair(Of String, List(Of CompFile)) In Dict
                 If Not Pair.Value.Any() Then Continue For
                 If Not Pair.Value.Any() Then Continue For
                 '增加卡片
                 '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(Project.Type = CompType.ModPack, 9, 8)} 'FUTURE: Res
+                Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(PageType = CompType.ModPack, 9, 8)} '9 是安装,8 是另存为
                 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}
                 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.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
                 NewCard.SwapControl = NewStack
@@ -178,18 +203,18 @@
                 If Pair.Key = TargetCardName OrElse
                 If Pair.Key = TargetCardName OrElse
                    (FrmMain.PageCurrent.Additional IsNot Nothing AndAlso '#2761
                    (FrmMain.PageCurrent.Additional IsNot Nothing AndAlso '#2761
                    CType(FrmMain.PageCurrent.Additional(1), List(Of String)).Contains(NewCard.Title)) Then
                    CType(FrmMain.PageCurrent.Additional(1), List(Of String)).Contains(NewCard.Title)) Then
-                    MyCard.StackInstall(NewStack, If(Project.Type = CompType.ModPack, 9, 8), Pair.Key) 'FUTURE: Res
+                    MyCard.StackInstall(NewStack, If(PageType = CompType.ModPack, 9, 8), Pair.Key) '9 是安装,8 是另存为
                 Else
                 Else
-                    NewCard.IsSwaped = True
+                    NewCard.IsSwapped = True
                 End If
                 End If
                 '增加提示
                 '增加提示
                 If Pair.Key = "其他版本" Then
                 If Pair.Key = "其他版本" Then
-                    NewStack.Children.Add(New MyHint With {.Text = "由于版本信息更新缓慢,可能无法识别刚更新的 MC 版本,只需等待几天即可自动恢复正常。", .IsWarn = False, .Margin = New Thickness(0, 0, 0, 7)})
+                    NewStack.Children.Add(New MyHint With {.Text = "由于版本信息更新缓慢,可能无法识别刚更新的 MC 版本。几天后即可正常识别。", .Theme = MyHint.Themes.Yellow, .Margin = New Thickness(5, 0, 0, 8)})
                 End If
                 End If
             Next
             Next
             '如果只有一张卡片,展开第一张卡片
             '如果只有一张卡片,展开第一张卡片
             If PanResults.Children.Count = 1 Then
             If PanResults.Children.Count = 1 Then
-                CType(PanResults.Children(0), MyCard).IsSwaped = False
+                CType(PanResults.Children(0), MyCard).IsSwapped = False
             End If
             End If
         Catch ex As Exception
         Catch ex As Exception
             Log(ex, "可视化工程下载列表出错", LogLevel.Feedback)
             Log(ex, "可视化工程下载列表出错", LogLevel.Feedback)
@@ -237,7 +262,7 @@
         AniControlEnabled -= 1
         AniControlEnabled -= 1
     End Sub
     End Sub
 
 
-    '整合包下载(安装)
+    '整合包安装
     Public Sub Install_Click(sender As MyListItem, e As EventArgs)
     Public Sub Install_Click(sender As MyListItem, e As EventArgs)
         Try
         Try
 
 
@@ -250,7 +275,7 @@
             Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
             Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
             If Validate.Validate(PackName) <> "" Then PackName = ""
             If Validate.Validate(PackName) <> "" Then PackName = ""
             Dim VersionName As String = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate})
             Dim VersionName As String = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate})
-            If String.IsNullOrEmpty(VersionName) Then Exit Sub
+            If String.IsNullOrEmpty(VersionName) Then Return
 
 
             '构造步骤加载器
             '构造步骤加载器
             Dim Loaders As New List(Of LoaderBase)
             Dim Loaders As New List(Of LoaderBase)
@@ -265,11 +290,11 @@
             Sub(MyLoader)
             Sub(MyLoader)
                 Select Case MyLoader.State
                 Select Case MyLoader.State
                     Case LoadState.Failed
                     Case LoadState.Failed
-                        Hint(MyLoader.Name & "失败:" & GetExceptionSummary(MyLoader.Error), HintType.Critical)
+                        Hint(MyLoader.Name & "失败:" & MyLoader.Error.GetBrief(), HintType.Critical)
                     Case LoadState.Aborted
                     Case LoadState.Aborted
                         Hint(MyLoader.Name & "已取消!", HintType.Info)
                         Hint(MyLoader.Name & "已取消!", HintType.Info)
                     Case LoadState.Loading
                     Case LoadState.Loading
-                        Exit Sub '不重新加载版本列表
+                        Return '不重新加载版本列表
                 End Select
                 End Select
                 McInstallFailedClearFolder(MyLoader)
                 McInstallFailedClearFolder(MyLoader)
             End Sub}
             End Sub}
@@ -282,77 +307,89 @@
             Log(ex, "下载资源整合包失败", LogLevel.Feedback)
             Log(ex, "下载资源整合包失败", LogLevel.Feedback)
         End Try
         End Try
     End Sub
     End Sub
-    'Mod、资源包下载;整合包另存为
-    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)
     Public Sub Save_Click(sender As Object, e As EventArgs)
         Dim File As CompFile = If(TypeOf sender Is MyListItem, sender, sender.Parent).Tag
         Dim File As CompFile = If(TypeOf sender Is MyListItem, sender, sender.Parent).Tag
         RunInNewThread(
         RunInNewThread(
         Sub()
         Sub()
             Try
             Try
-                Dim Desc As String = If(Project.Type = CompType.ModPack, "整合包", If(Project.Type = CompType.Mod, "Mod ", "资源包"))
+                Dim Desc As String = Nothing
+                Select Case File.Type
+                    Case CompType.ModPack : Desc = "整合包"
+                    Case CompType.Mod : Desc = "Mod "
+                    Case CompType.ResourcePack : Desc = "资源包"
+                    Case CompType.Shader : Desc = "光影包"
+                    Case CompType.DataPack : Desc = "数据包"
+                End Select
                 '确认默认保存位置
                 '确认默认保存位置
                 Dim DefaultFolder As String = Nothing
                 Dim DefaultFolder As String = Nothing
-                If Project.Type = CompType.Mod Then
-                    '获取 Mod 所需的加载器种类
-                    Dim AllowForge As Boolean? = Nothing, AllowFabric As Boolean? = Nothing
-                    If File.ModLoaders.Any Then '从文件中获取
-                        AllowForge = File.ModLoaders.Contains(CompModLoaderType.Forge) OrElse File.ModLoaders.Contains(CompModLoaderType.NeoForge)
-                        AllowFabric = File.ModLoaders.Contains(CompModLoaderType.Fabric)
-                    ElseIf Project.ModLoaders.Any Then '从工程中获取
-                        AllowForge = Project.ModLoaders.Contains(CompModLoaderType.Forge) OrElse File.ModLoaders.Contains(CompModLoaderType.NeoForge)
-                        AllowFabric = Project.ModLoaders.Contains(CompModLoaderType.Fabric)
-                    End If
-                    If AllowForge IsNot Nothing AndAlso Not AllowForge AndAlso
-                       AllowFabric IsNot Nothing AndAlso Not AllowFabric Then
-                        AllowForge = Nothing : AllowFabric = Nothing
+                If File.Type <> CompType.ModPack Then
+                    Dim SubFolder As String = Nothing
+                    Select Case Project.Type
+                        Case CompType.Mod : SubFolder = "mods\"
+                        Case CompType.ResourcePack : SubFolder = "resourcepacks\"
+                        Case CompType.Shader : SubFolder = "shaderpacks\"
+                        Case CompType.DataPack : SubFolder = "" '导航到版本根目录
+                    End Select
+                    Dim IsVersionSuitable As Func(Of McVersion, Boolean) = Nothing
+                    '获取资源所需的加载器
+                    Dim AllowedLoaders As New List(Of CompModLoaderType)
+                    If File.ModLoaders.Any Then
+                        AllowedLoaders = File.ModLoaders
+                    ElseIf Project.ModLoaders.Any Then
+                        AllowedLoaders = Project.ModLoaders
                     End If
                     End If
-                    Log("[Comp] 允许 Forge:" & If(AllowForge, "未知") & ",允许 Fabric:" & If(AllowFabric, "未知"))
-                    '判断某个版本是否符合要求
-                    Dim IsVersionSuitable As Func(Of McVersion, Boolean) =
+                    Log($"[Comp] {Desc}要求的加载器种类:" & If(AllowedLoaders.Any(), AllowedLoaders.Join(" / "), "无要求"))
+                    '判断某个版本是否符合资源要求
+                    IsVersionSuitable =
                     Function(Version)
                     Function(Version)
                         If Not Version.IsLoaded Then Version.Load()
                         If Not Version.IsLoaded Then Version.Load()
-                        If Not Version.Modable Then Return False
-                        If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso
-                           Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Version.Version.McName) Then Return False
-                        If AllowForge Is Nothing OrElse AllowFabric Is Nothing Then Return True
-                        If AllowForge AndAlso (Version.Version.HasForge OrElse Version.Version.HasNeoForge) Then Return True
-                        If AllowFabric AndAlso Version.Version.HasFabric Then Return True
+                        '只对 Mod 和数据包进行版本检测
+                        If Project.Type = CompType.Mod OrElse Project.Type = CompType.DataPack Then
+                            If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso
+                               Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Version.Version.McName) Then Return False
+                        End If
+                        '加载器
+                        If Not AllowedLoaders.Any() Then Return True '无要求
+                        If AllowedLoaders.Contains(CompModLoaderType.Forge) AndAlso Version.Version.HasForge Then Return True
+                        If AllowedLoaders.Contains(CompModLoaderType.Fabric) AndAlso Version.Version.HasFabric Then Return True
+                        If AllowedLoaders.Contains(CompModLoaderType.NeoForge) AndAlso Version.Version.HasNeoForge Then Return True
+                        If AllowedLoaders.Contains(CompModLoaderType.LiteLoader) AndAlso Version.Version.HasLiteLoader Then Return True
                         Return False
                         Return False
                     End Function
                     End Function
-                    '获取 Mod 默认下载位置
-                    If CachedFolder IsNot Nothing Then
-                        DefaultFolder = CachedFolder
-                        Log("[Comp] 使用上次下载时的文件夹作为默认下载位置")
+                    '获取常规资源默认下载位置
+                    If CachedFolder.ContainsKey(Project.Type) Then
+                        DefaultFolder = CachedFolder(Project.Type)
+                        Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}")
                     ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
                     ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
-                        DefaultFolder = McVersionCurrent.PathIndie & "mods\"
+                        DefaultFolder = $"{McVersionCurrent.PathIndie}{SubFolder}"
                         Directory.CreateDirectory(DefaultFolder)
                         Directory.CreateDirectory(DefaultFolder)
-                        Log("[Comp] 使用当前版本的 mods 文件夹作为默认下载位置(" & McVersionCurrent.Name & ")")
+                        Log($"[Comp] 使用当前版本作为默认下载位置:{DefaultFolder}")
                     Else
                     Else
+                        '查找所有可能的版本
                         Dim NeedLoad As Boolean = McVersionListLoader.State <> LoadState.Finished
                         Dim NeedLoad As Boolean = McVersionListLoader.State <> LoadState.Finished
                         If NeedLoad Then
                         If NeedLoad Then
                             Hint("正在查找适合的游戏版本……")
                             Hint("正在查找适合的游戏版本……")
                             LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True)
                             LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True)
                         End If
                         End If
-                        Dim SuitableVersions As New List(Of McVersion)
-                        For Each Version As McVersion In McVersionList.Values.SelectMany(Function(l) l)
-                            If IsVersionSuitable(Version) Then SuitableVersions.Add(Version)
-                        Next
-                        If Not SuitableVersions.Any() Then
+                        Dim SuitableVersions = McVersionList.Values.SelectMany(Function(l) l).Where(Function(v) IsVersionSuitable(v)).
+                            Select(Function(v) New DirectoryInfo($"{v.PathIndie}{SubFolder}"))
+                        If SuitableVersions.Any Then
+                            Dim SelectedVersion = SuitableVersions.
+                                OrderByDescending(Function(Dir) If(Dir.Exists, Dir.LastWriteTimeUtc, Date.MinValue)). '先按文件夹更改时间降序
+                                ThenByDescending(Function(Dir) If(Dir.Exists, Dir.GetFiles().Length, -1)). '再按文件夹中的文件数量降序
+                                First()
+                            DefaultFolder = SelectedVersion.FullName
+                            Directory.CreateDirectory(DefaultFolder)
+                            Log($"[Comp] 使用适合的游戏版本作为默认下载位置:{DefaultFolder}")
+                        Else
                             DefaultFolder = PathMcFolder
                             DefaultFolder = PathMcFolder
                             If NeedLoad Then
                             If NeedLoad Then
-                                Hint("当前 MC 文件夹中没有找到适合这个 Mod 的版本!")
+                                Hint("当前 MC 文件夹中没有找到适合此资源的版本!")
                             Else
                             Else
                                 Log("[Comp] 由于当前版本不兼容,使用当前的 MC 文件夹作为默认下载位置")
                                 Log("[Comp] 由于当前版本不兼容,使用当前的 MC 文件夹作为默认下载位置")
                             End If
                             End If
-                        Else '选择 Mod 数量最多的版本
-                            Dim SelectedVersion = SuitableVersions.OrderBy(
-                            Function(v)
-                                Dim Info As New DirectoryInfo(v.PathIndie & "mods\")
-                                Return If(Info.Exists, Info.GetFiles().Length, -1)
-                            End Function).LastOrDefault()
-                            DefaultFolder = SelectedVersion.PathIndie & "mods\"
-                            Directory.CreateDirectory(DefaultFolder)
-                            Log("[Comp] 使用适合的游戏版本作为默认下载位置(" & SelectedVersion.Name & ")")
                         End If
                         End If
                     End If
                     End If
                 End If
                 End If
@@ -382,13 +419,13 @@
                     Dim Target As String
                     Dim Target As String
                     Target = SelectSaveFile("选择保存位置", FileName,
                     Target = SelectSaveFile("选择保存位置", FileName,
                         Desc & "文件|" &
                         Desc & "文件|" &
-                        If(Project.Type = CompType.Mod,
+                        If(File.Type = CompType.Mod,
                             If(File.FileName.EndsWith(".litemod"), "*.litemod", "*.jar"),
                             If(File.FileName.EndsWith(".litemod"), "*.litemod", "*.jar"),
                             If(File.FileName.EndsWith(".mrpack"), "*.mrpack", "*.zip")), DefaultFolder)
                             If(File.FileName.EndsWith(".mrpack"), "*.mrpack", "*.zip")), DefaultFolder)
-                    If Not Target.Contains("\") Then Exit Sub
+                    If Not Target.Contains("\") Then Return
                     '构造步骤加载器
                     '构造步骤加载器
-                    Dim LoaderName As String = Desc & "下载:" & GetFileNameWithoutExtentionFromPath(Target) & " "
-                    If Target <> DefaultFolder AndAlso Project.Type = CompType.Mod Then CachedFolder = GetPathFromFullPath(Target)
+                    Dim LoaderName As String = Desc & "下载:" & IO.Path.GetFileNameWithoutExtension(Target) & " "
+                    If Target <> DefaultFolder Then CachedFolder(Project.Type) = GetPathFromFullPath(Target)
                     Dim Loaders As New List(Of LoaderBase)
                     Dim Loaders As New List(Of LoaderBase)
                     Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True})
                     Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True})
                     '启动
                     '启动

+ 26 - 0
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml

@@ -0,0 +1,26 @@
+<local:MyPageRight
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+    mc:Ignorable="d" x:Class="PageDownloadDataPack"
+    PanScroll="Content">
+    <local:PageComp x:Name="Content" PageType="DataPack" TypeName="数据包" TypeNameSpaced="数据包">
+        <local:MyComboBoxItem Tag="" Content="全部" IsSelected="True" />
+        <local:MyComboBoxItem Content="世界元素" Tag="/worldgen" />
+        <local:MyComboBoxItem Content="科技" Tag="6951/technology" />
+        <local:MyComboBoxItem Content="游戏机制" Tag="/game-mechanics" />
+        <local:MyComboBoxItem Content="运输" Tag="/transportation" />
+        <local:MyComboBoxItem Content="仓储" Tag="/storage" />
+        <local:MyComboBoxItem Content="魔法" Tag="6952/magic" />
+        <local:MyComboBoxItem Content="冒险" Tag="6948/adventure" />
+        <local:MyComboBoxItem Content="幻想" Tag="6949/" />
+        <local:MyComboBoxItem Content="装饰" Tag="/decoration" />
+        <local:MyComboBoxItem Content="生物" Tag="/mobs" />
+        <local:MyComboBoxItem Content="实用" Tag="6953/utility" />
+        <local:MyComboBoxItem Content="装备与工具" Tag="/equipment" />
+        <local:MyComboBoxItem Content="性能优化" Tag="/optimization" />
+        <local:MyComboBoxItem Content="服务器" Tag="/social" />
+        <local:MyComboBoxItem Content="支持库" Tag="6950/library" />
+        <local:MyComboBoxItem Content="Mod 相关" Tag="6946/" />
+    </local:PageComp>
+</local:MyPageRight>

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

@@ -0,0 +1,2 @@
+Public Class PageDownloadDataPack
+End Class

+ 35 - 0
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml

@@ -0,0 +1,35 @@
+<local:MyPageRight
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+    mc:Ignorable="d" x:Class="PageDownloadMod"
+    PanScroll="Content">
+    <local:PageComp x:Name="Content" PageType="Mod" TypeName="Mod" TypeNameSpaced=" Mod ">
+        <local:MyComboBoxItem Content="全部" Tag="" IsSelected="True" />
+        <local:MyComboBoxItem Content="世界元素" Tag="406/worldgen" />
+        <local:MyComboBoxItem Content="生物群系" Tag="407/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="维度" Tag="410/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="矿物与资源" Tag="408/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="天然结构" Tag="409/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="科技" Tag="412/technology" />
+        <local:MyComboBoxItem Content="管道与物流" Tag="415/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="自动化" Tag="4843/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="能源" Tag="417/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="红石" Tag="4558/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="食物与烹饪" Tag="436/food" />
+        <local:MyComboBoxItem Content="农业" Tag="416/" Margin="15,0,0,0" />
+        <local:MyComboBoxItem Content="游戏机制" Tag="/game-mechanics" />
+        <local:MyComboBoxItem Content="运输" Tag="414/transportation" />
+        <local:MyComboBoxItem Content="仓储" Tag="420/storage" />
+        <local:MyComboBoxItem Content="魔法" Tag="419/magic" />
+        <local:MyComboBoxItem Content="冒险" Tag="422/adventure" />
+        <local:MyComboBoxItem Content="装饰" Tag="424/decoration" />
+        <local:MyComboBoxItem Content="生物" Tag="411/mobs" />
+        <local:MyComboBoxItem Content="实用" Tag="5191/utility" />
+        <local:MyComboBoxItem Content="装备与工具" Tag="434/equipment" />
+        <local:MyComboBoxItem Content="性能优化" Tag="/optimization" />
+        <local:MyComboBoxItem Content="信息显示" Tag="423/" />
+        <local:MyComboBoxItem Content="服务器" Tag="435/social" />
+        <local:MyComboBoxItem Content="支持库" Tag="421/library" />
+    </local:PageComp>
+</local:MyPageRight>

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

@@ -0,0 +1,2 @@
+Public Class PageDownloadMod
+End Class

+ 28 - 0
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml

@@ -0,0 +1,28 @@
+<local:MyPageRight
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+    mc:Ignorable="d" x:Class="PageDownloadPack"
+    PanScroll="Content">
+    <local:PageComp x:Name="Content" PageType="ModPack" TypeName="整合包" TypeNameSpaced="整合包">
+        <local:MyComboBoxItem Tag="" Content="全部" IsSelected="True" />
+        <local:MyComboBoxItem Tag="4484/" Content="多人" />
+        <local:MyComboBoxItem Tag="/optimization" Content="性能优化" />
+        <local:MyComboBoxItem Tag="4479/challenging" Content="硬核" />
+        <local:MyComboBoxItem Tag="4483/combat" Content="战斗" />
+        <local:MyComboBoxItem Tag="4478/quests" Content="任务" />
+        <local:MyComboBoxItem Tag="4472/technology" Content="科技" />
+        <local:MyComboBoxItem Tag="4473/magic" Content="魔法" />
+        <local:MyComboBoxItem Tag="4475/adventure" Content="冒险" />
+        <local:MyComboBoxItem Tag="/kitchen-sink" Content="水槽包" />
+        <local:MyComboBoxItem Tag="4476/" Content="探索" />
+        <local:MyComboBoxItem Tag="4477/" Content="小游戏" />
+        <local:MyComboBoxItem Tag="4474/" Content="科幻" />
+        <local:MyComboBoxItem Tag="4736/" Content="空岛" />
+        <local:MyComboBoxItem Tag="5128/" Content="原版改良" />
+        <local:MyComboBoxItem Tag="4487/" Content="FTB" ToolTip="由 FTB 官方发布" ToolTipService.HorizontalOffset="70" />
+        <local:MyComboBoxItem Tag="4480/" Content="基于地图" />
+        <local:MyComboBoxItem Tag="4481/lightweight" Content="轻量整合" />
+        <local:MyComboBoxItem Tag="4482/" Content="大型整合" />
+    </local:PageComp>
+</local:MyPageRight>

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

@@ -0,0 +1,2 @@
+Public Class PageDownloadPack
+End Class

+ 45 - 0
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml

@@ -0,0 +1,45 @@
+<local:MyPageRight
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+    mc:Ignorable="d" x:Class="PageDownloadResourcePack"
+    PanScroll="Content">
+    <local:PageComp x:Name="Content" PageType="ResourcePack" TypeName="资源包" TypeNameSpaced="资源包">
+        <local:MyComboBoxItem Tag="" Content="全部" IsSelected="True" />
+
+        <local:MyComboBoxItem Content="风格" IsEnabled="False" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="原版风" Tag="403/vanilla-like" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="写实风" Tag="400/realistic" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="现代风" Tag="401/" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="中世纪" Tag="402/" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="蒸汽朋克" Tag="399/" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="主题化" Tag="/themed" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="简洁" Tag="/simplistic" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="装饰" Tag="/decoration" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="战斗" Tag="/combat" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="实用" Tag="/utility" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="改良" Tag="/tweaks" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="鬼畜" Tag="/cursed" />
+
+        <local:MyComboBoxItem Content="特性" IsEnabled="False" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="含实体" Tag="/entities" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="含声音" Tag="/audio" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="含字体" Tag="5244/fonts" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="含模型" Tag="/models" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="含语言" Tag="/locale" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="含 UI" Tag="/gui" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="核心着色器" Tag="/core-shaders" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="动态效果" Tag="404/" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="兼容 Mod" Tag="4465/modded" />
+
+        <local:MyComboBoxItem Content="分辨率" IsEnabled="False" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="8x 或更低" Tag="/8x-" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="16x" Tag="393/16x" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="32x" Tag="394/32x" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="48x" Tag="/48x" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="64x" Tag="395/64x" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="128x" Tag="396/128x" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="256x" Tag="397/256x" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="512x 或更高" Tag="398/512x+" />
+    </local:PageComp>
+</local:MyPageRight>

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

@@ -0,0 +1,2 @@
+Public Class PageDownloadResourcePack
+End Class

+ 34 - 0
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml

@@ -0,0 +1,34 @@
+<local:MyPageRight
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+    mc:Ignorable="d" x:Class="PageDownloadShader"
+    PanScroll="Content">
+    <local:PageComp x:Name="Content" PageType="Shader" TypeName="光影包" TypeNameSpaced="光影包">
+        <local:MyComboBoxItem Tag="" Content="全部" IsSelected="True" />
+        
+        <local:MyComboBoxItem Content="风格" IsEnabled="False" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="原版风" Tag="6555/vanilla-like" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="幻想风" Tag="6554/fantasy" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="写实风" Tag="6553/realistic" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="半写实风" Tag="/semi-realistic" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="卡通风" Tag="/cartoon" />
+
+        <local:MyComboBoxItem Content="特性" IsEnabled="False" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="彩色光照" Tag="/colored-lighting" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="路径追踪" Tag="/path-tracing" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="PBR" Tag="/pbr" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="反射" Tag="/reflections" />
+
+        <local:MyComboBoxItem Content="性能负荷" IsEnabled="False" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="极低" Tag="/potato" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="低" Tag="/low" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="中" Tag="/medium" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="高" Tag="/high" />
+
+        <local:MyComboBoxItem Content="加载器" IsEnabled="False" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="原版可用" Tag="/vanilla" ToolTip="作为资源包加载,可以直接在原版游戏里使用。" ToolTipService.HorizontalOffset="70" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="Iris" Tag="/iris" />
+        <local:MyComboBoxItem Margin="15,0,0,0" Content="OptiFine" Tag="/optifine" />
+    </local:PageComp>
+</local:MyPageRight>

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

@@ -0,0 +1,2 @@
+Public Class PageDownloadShader
+End Class

+ 109 - 96
Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb

@@ -217,14 +217,14 @@ Public Module ModDownloadLib
             Dim Id = Version.Title
             Dim Id = Version.Title
             Dim JsonUrl = Version.Tag("url").ToString
             Dim JsonUrl = Version.Tag("url").ToString
             Dim VersionFolder As String = SelectFolder()
             Dim VersionFolder As String = SelectFolder()
-            If Not VersionFolder.Contains("\") Then Exit Sub
+            If Not VersionFolder.Contains("\") Then Return
             VersionFolder = VersionFolder & Id & "\"
             VersionFolder = VersionFolder & Id & "\"
 
 
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For
                 If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For
                 Hint("该服务端正在下载中!", HintType.Critical)
                 Hint("该服务端正在下载中!", HintType.Critical)
-                Exit Sub
+                Return
             Next
             Next
 
 
             Dim Loaders As New List(Of LoaderBase)
             Dim Loaders As New List(Of LoaderBase)
@@ -244,7 +244,7 @@ Public Module ModDownloadLib
                         Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Critical)
                         Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Critical)
                         Thread.Sleep(2000) '等玩家把上一个提示看完
                         Thread.Sleep(2000) '等玩家把上一个提示看完
                         Task.Abort()
                         Task.Abort()
-                        Exit Sub
+                        Return
                     End If
                     End If
                     Dim JarUrl As String = McVersion.JsonObject("downloads")("server")("url")
                     Dim JarUrl As String = McVersion.JsonObject("downloads")("server")("url")
                     Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(McVersion.JsonObject("downloads")("server")("size"), -1), Hash:=McVersion.JsonObject("downloads")("server")("sha1"))
                     Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(McVersion.JsonObject("downloads")("server")("size"), -1), Hash:=McVersion.JsonObject("downloads")("server")("sha1"))
@@ -294,14 +294,14 @@ pause"
             Dim Id = Version.Title
             Dim Id = Version.Title
             Dim JsonUrl = Version.Tag("url").ToString
             Dim JsonUrl = Version.Tag("url").ToString
             Dim VersionFolder As String = SelectFolder()
             Dim VersionFolder As String = SelectFolder()
-            If Not VersionFolder.Contains("\") Then Exit Sub
+            If Not VersionFolder.Contains("\") Then Return
             VersionFolder = VersionFolder & Id & "\"
             VersionFolder = VersionFolder & Id & "\"
 
 
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
                 If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
                 Hint("该版本正在下载中!", HintType.Critical)
                 Hint("该版本正在下载中!", HintType.Critical)
-                Exit Sub
+                Return
             Next
             Next
 
 
             Dim Loaders As New List(Of LoaderBase)
             Dim Loaders As New List(Of LoaderBase)
@@ -383,7 +383,7 @@ pause"
             WikiName = "Java版Pre-classic_" & Id
             WikiName = "Java版Pre-classic_" & Id
         Else
         Else
             Log("[Error] 未知的版本格式:" & Id & "。", LogLevel.Feedback)
             Log("[Error] 未知的版本格式:" & Id & "。", LogLevel.Feedback)
-            Exit Sub
+            Return
         End If
         End If
         OpenWebsite("https://zh.minecraft.wiki/w/" & WikiName.Replace("_experimental-snapshot-", "-exp"))
         OpenWebsite("https://zh.minecraft.wiki/w/" & WikiName.Replace("_experimental-snapshot-", "-exp"))
     End Sub
     End Sub
@@ -396,13 +396,13 @@ pause"
         Try
         Try
             Dim Id As String = DownloadInfo.NameVersion
             Dim Id As String = DownloadInfo.NameVersion
             Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.NameFile, "OptiFine Jar (*.jar)|*.jar")
             Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.NameFile, "OptiFine Jar (*.jar)|*.jar")
-            If Not Target.Contains("\") Then Exit Sub
+            If Not Target.Contains("\") Then Return
 
 
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"OptiFine {DownloadInfo.NameDisplay} 下载" Then Continue For
                 If OngoingLoader.Name <> $"OptiFine {DownloadInfo.NameDisplay} 下载" Then Continue For
                 Hint("该版本正在下载中!", HintType.Critical)
                 Hint("该版本正在下载中!", HintType.Critical)
-                Exit Sub
+                Return
             Next
             Next
 
 
             Dim Loader As New LoaderCombo(Of DlOptiFineListEntry)("OptiFine " & DownloadInfo.NameDisplay & " 下载", McDownloadOptiFineSaveLoader(DownloadInfo, Target)) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
             Dim Loader As New LoaderCombo(Of DlOptiFineListEntry)("OptiFine " & DownloadInfo.NameDisplay & " 下载", McDownloadOptiFineSaveLoader(DownloadInfo, Target)) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
@@ -434,7 +434,7 @@ pause"
                 End Try
                 End Try
                 '检查下载结果
                 '检查下载结果
                 Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
                 Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
-                If Task.IsAborted Then Exit Sub
+                If Task.IsAborted Then Return
                 If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
                 If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
             End If
             End If
         End SyncLock
         End SyncLock
@@ -449,7 +449,7 @@ pause"
         '开始启动
         '开始启动
         SyncLock InstallSyncLock
         SyncLock InstallSyncLock
             Dim Info = New ProcessStartInfo With {
             Dim Info = New ProcessStartInfo With {
-                .FileName = Java.PathJavaw,
+                .FileName = Java.PathJava,
                 .Arguments = Arguments,
                 .Arguments = Arguments,
                 .UseShellExecute = False,
                 .UseShellExecute = False,
                 .CreateNoWindow = True,
                 .CreateNoWindow = True,
@@ -570,7 +570,8 @@ pause"
             '官方源
             '官方源
             Dim PageData As String
             Dim PageData As String
             Try
             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", SimulateBrowserHeaders:=True)
                 Task.Progress = 0.8
                 Task.Progress = 0.8
                 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
                 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
                 Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
                 Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -584,7 +585,7 @@ pause"
         Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("等待原版下载",
         Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("等待原版下载",
         Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
         Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
             '等待原版文件下载完成
             '等待原版文件下载完成
-            If ClientDownloadLoader Is Nothing Then Exit Sub
+            If ClientDownloadLoader Is Nothing Then Return
             Dim TargetLoaders As List(Of LoaderBase) =
             Dim TargetLoaders As List(Of LoaderBase) =
                ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName).
                ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName).
                Where(Function(l) l.State <> LoadState.Finished).ToList
                Where(Function(l) l.State <> LoadState.Finished).ToList
@@ -593,9 +594,9 @@ pause"
                 TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
                 TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
                 Thread.Sleep(50)
                 Thread.Sleep(50)
             Loop
             Loop
-            If Task.IsAborted Then Exit Sub
+            If Task.IsAborted Then Return
             '拷贝原版文件
             '拷贝原版文件
-            If Not IsCustomFolder Then Exit Sub
+            If Not IsCustomFolder Then Return
             SyncLock VanillaSyncLock
             SyncLock VanillaSyncLock
                 Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
                 Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
                 Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit)
                 Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit)
@@ -707,7 +708,7 @@ Retry:
         '下载支持库
         '下载支持库
         If FixLibrary Then
         If FixLibrary Then
             Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 OptiFine 支持库文件",
             Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 OptiFine 支持库文件",
-                                                                Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False})
+                Sub(Task) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False})
             Loaders.Add(New LoaderDownload("下载 OptiFine 支持库文件", New List(Of NetFile)) With {.ProgressWeight = 4})
             Loaders.Add(New LoaderDownload("下载 OptiFine 支持库文件", New List(Of NetFile)) With {.ProgressWeight = 4})
         End If
         End If
 
 
@@ -733,7 +734,8 @@ Retry:
             '官方源
             '官方源
             Dim PageData As String
             Dim PageData As String
             Try
             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", SimulateBrowserHeaders:=True)
                 Task.Progress = 0.8
                 Task.Progress = 0.8
                 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
                 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
                 Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
                 Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -831,7 +833,7 @@ Retry:
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
                 If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
                 Hint("该版本正在下载中!", HintType.Critical)
                 Hint("该版本正在下载中!", HintType.Critical)
-                Exit Sub
+                Return
             Next
             Next
 
 
             '已有版本检查
             '已有版本检查
@@ -840,7 +842,7 @@ Retry:
                     File.Delete(VersionFolder & VersionName & ".jar")
                     File.Delete(VersionFolder & VersionName & ".jar")
                     File.Delete(VersionFolder & VersionName & ".json")
                     File.Delete(VersionFolder & VersionName & ".json")
                 Else
                 Else
-                    Exit Sub
+                    Return
                 End If
                 End If
             End If
             End If
 
 
@@ -859,13 +861,13 @@ Retry:
         Try
         Try
             Dim Id As String = DownloadInfo.Inherit
             Dim Id As String = DownloadInfo.Inherit
             Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.FileName.Replace("-SNAPSHOT", ""), "LiteLoader 安装器 (*.jar)|*.jar")
             Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.FileName.Replace("-SNAPSHOT", ""), "LiteLoader 安装器 (*.jar)|*.jar")
-            If Not Target.Contains("\") Then Exit Sub
+            If Not Target.Contains("\") Then Return
 
 
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
                 If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
                 Hint("该版本正在下载中!", HintType.Critical)
                 Hint("该版本正在下载中!", HintType.Critical)
-                Exit Sub
+                Return
             Next
             Next
 
 
             '构造步骤加载器
             '构造步骤加载器
@@ -1041,13 +1043,13 @@ Retry:
             Dim Target As String = SelectSaveFile("选择保存位置", $"{Info.LoaderName}-{Info.Inherit}-{Info.VersionName}.{Info.FileExtension}",
             Dim Target As String = SelectSaveFile("选择保存位置", $"{Info.LoaderName}-{Info.Inherit}-{Info.VersionName}.{Info.FileExtension}",
                                             $"{Info.LoaderName} 安装器 (*.{Info.FileExtension})|*.{Info.FileExtension}")
                                             $"{Info.LoaderName} 安装器 (*.{Info.FileExtension})|*.{Info.FileExtension}")
             Dim DisplayName As String = $"{Info.LoaderName} {Info.Inherit} - {Info.VersionName}"
             Dim DisplayName As String = $"{Info.LoaderName} {Info.Inherit} - {Info.VersionName}"
-            If Not Target.Contains("\") Then Exit Sub
+            If Not Target.Contains("\") Then Return
 
 
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"{DisplayName} 下载" Then Continue For
                 If OngoingLoader.Name <> $"{DisplayName} 下载" Then Continue For
                 Hint("该版本正在下载中!", HintType.Critical)
                 Hint("该版本正在下载中!", HintType.Critical)
-                Exit Sub
+                Return
             Next
             Next
 
 
             '获取下载地址
             '获取下载地址
@@ -1103,7 +1105,7 @@ Retry:
                 End Try
                 End Try
                 '检查下载结果
                 '检查下载结果
                 Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 60))
                 Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 60))
-                If Task.IsAborted Then Exit Sub
+                If Task.IsAborted Then Return
                 If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
                 If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
             End If
             End If
         End SyncLock
         End SyncLock
@@ -1118,7 +1120,7 @@ Retry:
         '开始启动
         '开始启动
         SyncLock InstallSyncLock
         SyncLock InstallSyncLock
             Dim Info = New ProcessStartInfo With {
             Dim Info = New ProcessStartInfo With {
-                .FileName = Java.PathJavaw,
+                .FileName = Java.PathJava,
                 .Arguments = Arguments,
                 .Arguments = Arguments,
                 .UseShellExecute = False,
                 .UseShellExecute = False,
                 .CreateNoWindow = True,
                 .CreateNoWindow = True,
@@ -1188,8 +1190,8 @@ Retry:
                     outputWaitHandle.WaitOne(10000)
                     outputWaitHandle.WaitOne(10000)
                     errorWaitHandle.WaitOne(10000)
                     errorWaitHandle.WaitOne(10000)
                     process.Dispose()
                     process.Dispose()
-                    '检查是否安装成功:最后两行中是否有 true(true 可能在倒数第二行,见 #832)
-                    If LastResults.Last = "true" OrElse (LastResults.Count >= 2 AndAlso LastResults(LastResults.Count - 2) = "true") Then Exit Sub
+                    '检查是否安装成功:最后 5 行中是否有 true(true 可能在倒数数行,见 #832)
+                    If LastResults.Reverse().Take(5).Any(Function(l) l = "true") Then Return
                     Log(Join(LastResults, vbCrLf))
                     Log(Join(LastResults, vbCrLf))
                     Dim LastLines As String = ""
                     Dim LastLines As String = ""
                     For i As Integer = Math.Max(0, LastResults.Count - 5) To LastResults.Count - 1 '最后 5 行
                     For i As Integer = Math.Max(0, LastResults.Count - 5) To LastResults.Count - 1 '最后 5 行
@@ -1237,7 +1239,7 @@ Retry:
                 Task.Progress = 0.91
                 Task.Progress = 0.91
             Case Else
             Case Else
                 If ModeDebug Then Log("[Installer] " & Content)
                 If ModeDebug Then Log("[Installer] " & Content)
-                Exit Sub
+                Return
         End Select
         End Select
         Log("[Installer] " & Content)
         Log("[Installer] " & Content)
     End Sub
     End Sub
@@ -1282,7 +1284,7 @@ Retry:
                 For Each ForgeVersion In ForgeLoader.Output
                 For Each ForgeVersion In ForgeLoader.Output
                     If VersionSortInteger(ForgeVersion.Version.ToString, LoaderVersion) = 0 Then
                     If VersionSortInteger(ForgeVersion.Version.ToString, LoaderVersion) = 0 Then
                         Info = ForgeVersion
                         Info = ForgeVersion
-                        Exit Sub
+                        Return
                     End If
                     End If
                 Next
                 Next
                 Throw New Exception($"未能找到 {LoaderName} " & Inherit & "-" & LoaderVersion & " 的详细信息!")
                 Throw New Exception($"未能找到 {LoaderName} " & Inherit & "-" & LoaderVersion & " 的详细信息!")
@@ -1340,13 +1342,13 @@ Retry:
                     If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then
                     If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then
                         '下载原版 Json 文件
                         '下载原版 Json 文件
                         Task.Progress = 0.4
                         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]
                         '[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg]
                         Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).BeforeFirst("@")
                         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))
                         Dim Address = McLibGet(OriginalName).Replace(".jar", "-mappings." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1))
                         Dim ClientMappings As JToken = RawJson("downloads")("client_mappings")
                         Dim ClientMappings As JToken = RawJson("downloads")("client_mappings")
                         Libs.Add(New McLibToken With {
                         Libs.Add(New McLibToken With {
-                                 .IsJumpLoader = False, .IsNatives = False, .LocalPath = Address, .OriginalName = OriginalName,
+                                 .IsNatives = False, .LocalPath = Address, .OriginalName = OriginalName,
                                  .Url = ClientMappings("url"), .Size = ClientMappings("size"), .SHA1 = ClientMappings("sha1")})
                                  .Url = ClientMappings("url"), .Size = ClientMappings("size"), .SHA1 = ClientMappings("sha1")})
                         Log($"[Download] 需要下载 Mappings:{ClientMappings("url")} (SHA1: {ClientMappings("sha1")})")
                         Log($"[Download] 需要下载 Mappings:{ClientMappings("url")} (SHA1: {ClientMappings("sha1")})")
                     End If
                     End If
@@ -1385,7 +1387,7 @@ Retry:
 #End Region
 #End Region
 #Region "原版文件"
 #Region "原版文件"
                 '等待原版文件下载完成
                 '等待原版文件下载完成
-                If ClientDownloadLoader Is Nothing Then Exit Sub
+                If ClientDownloadLoader Is Nothing Then Return
                 Dim TargetLoaders As List(Of LoaderBase) =
                 Dim TargetLoaders As List(Of LoaderBase) =
                     ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName).
                     ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName).
                     Where(Function(l) l.State <> LoadState.Finished).ToList()
                     Where(Function(l) l.State <> LoadState.Finished).ToList()
@@ -1394,9 +1396,9 @@ Retry:
                     TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
                     TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
                     Thread.Sleep(50)
                     Thread.Sleep(50)
                 Loop
                 Loop
-                If Task.IsAborted Then Exit Sub
+                If Task.IsAborted Then Return
                 '拷贝原版文件
                 '拷贝原版文件
-                If Not IsCustomFolder Then Exit Sub
+                If Not IsCustomFolder Then Return
                 SyncLock VanillaSyncLock
                 SyncLock VanillaSyncLock
                     Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
                     Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
                     Directory.CreateDirectory(McFolder & "versions\" & Inherit)
                     Directory.CreateDirectory(McFolder & "versions\" & Inherit)
@@ -1639,32 +1641,33 @@ Retry:
     ''' 尝试刷新 Forge 推荐版本缓存。
     ''' 尝试刷新 Forge 推荐版本缓存。
     ''' </summary>
     ''' </summary>
     Public Sub McDownloadForgeRecommendedRefresh()
     Public Sub McDownloadForgeRecommendedRefresh()
-        If IsForgeRecommendedRefreshed Then Exit Sub
+        If IsForgeRecommendedRefreshed Then Return
         IsForgeRecommendedRefreshed = True
         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", RequireJson:=True)
+                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
     End Sub
     Private IsForgeRecommendedRefreshed As Boolean = False
     Private IsForgeRecommendedRefreshed As Boolean = False
 
 
@@ -1795,13 +1798,13 @@ Retry:
             Dim FileName As String = GetFileNameFromPath(Url)
             Dim FileName As String = GetFileNameFromPath(Url)
             Dim Version As String = GetFileNameFromPath(DownloadInfo("version").ToString)
             Dim Version As String = GetFileNameFromPath(DownloadInfo("version").ToString)
             Dim Target As String = SelectSaveFile("选择保存位置", FileName, "Fabric 安装器 (*.jar)|*.jar")
             Dim Target As String = SelectSaveFile("选择保存位置", FileName, "Fabric 安装器 (*.jar)|*.jar")
-            If Not Target.Contains("\") Then Exit Sub
+            If Not Target.Contains("\") Then Return
 
 
             '重复任务检查
             '重复任务检查
             For Each OngoingLoader In LoaderTaskbar.ToList()
             For Each OngoingLoader In LoaderTaskbar.ToList()
                 If OngoingLoader.Name <> $"Fabric {Version} 安装器下载" Then Continue For
                 If OngoingLoader.Name <> $"Fabric {Version} 安装器下载" Then Continue For
                 Hint("该版本正在下载中!", HintType.Critical)
                 Hint("该版本正在下载中!", HintType.Critical)
-                Exit Sub
+                Return
             Next
             Next
 
 
             '构造步骤加载器
             '构造步骤加载器
@@ -1988,7 +1991,7 @@ Retry:
             Case LoadState.Finished
             Case LoadState.Finished
                 Hint(Loader.Name & "成功!", HintType.Finish)
                 Hint(Loader.Name & "成功!", HintType.Finish)
             Case LoadState.Failed
             Case LoadState.Failed
-                Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
+                Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical)
             Case LoadState.Aborted
             Case LoadState.Aborted
                 Hint(Loader.Name & "已取消!", HintType.Info)
                 Hint(Loader.Name & "已取消!", HintType.Info)
         End Select
         End Select
@@ -2002,11 +2005,11 @@ Retry:
                 WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存)
                 WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存)
                 Hint(Loader.Name & "成功!", HintType.Finish)
                 Hint(Loader.Name & "成功!", HintType.Finish)
             Case LoadState.Failed
             Case LoadState.Failed
-                Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
+                Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical)
             Case LoadState.Aborted
             Case LoadState.Aborted
                 Hint(Loader.Name & "已取消!", HintType.Info)
                 Hint(Loader.Name & "已取消!", HintType.Info)
             Case LoadState.Loading
             Case LoadState.Loading
-                Exit Sub '不重新加载版本列表
+                Return '不重新加载版本列表
         End Select
         End Select
         McInstallFailedClearFolder(Loader)
         McInstallFailedClearFolder(Loader)
         LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
         LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
@@ -2055,7 +2058,7 @@ Retry:
     ''' 获取合并安装加载器列表,并进行前期的缓存清理与 Java 检查工作。
     ''' 获取合并安装加载器列表,并进行前期的缓存清理与 Java 检查工作。
     ''' </summary>
     ''' </summary>
     ''' <exception cref="CancelledException" />
     ''' <exception cref="CancelledException" />
-    Public Function McInstallLoader(Request As McInstallRequest, Optional DontFixLibraries As Boolean = False) As List(Of LoaderBase)
+    Public Function McInstallLoader(Request As McInstallRequest) As List(Of LoaderBase)
         '获取缓存目录(安装 Mod 加载器的文件夹不能包含空格)
         '获取缓存目录(安装 Mod 加载器的文件夹不能包含空格)
         Dim TempMcFolder As String = RequestTaskTempFolder(Request.OptiFineEntry IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing)
         Dim TempMcFolder As String = RequestTaskTempFolder(Request.OptiFineEntry IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing)
 
 
@@ -2086,17 +2089,13 @@ Retry:
         Dim LiteLoaderFolder As String = Nothing
         Dim LiteLoaderFolder As String = Nothing
         If Request.LiteLoaderEntry IsNot Nothing Then LiteLoaderFolder = TempMcFolder & "versions\" & Request.MinecraftName & "-LiteLoader"
         If Request.LiteLoaderEntry IsNot Nothing Then LiteLoaderFolder = TempMcFolder & "versions\" & Request.MinecraftName & "-LiteLoader"
 
 
-        '决定版本隔离情况(#5970)
-        Dim Modable As Boolean = Request.FabricVersion IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing
-        Dim Version As New McVersion(VersionFolder)
-        Version.InitPathIndie(Modable)
-        Dim ModsFolder As String = If(Modable, VersionFolder, PathMcFolder) & "mods\"
-
         '判断 OptiFine 是否作为 Mod 进行下载
         '判断 OptiFine 是否作为 Mod 进行下载
+        Dim Modable As Boolean = Request.FabricVersion IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing
+        Dim ModsTempFolder As String = TempMcFolder & "mods\"
         Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso Modable '选择了 OptiFine 与任意 Mod 加载器
         Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso Modable '选择了 OptiFine 与任意 Mod 加载器
         If OptiFineAsMod Then
         If OptiFineAsMod Then
             Log("[Download] OptiFine 将作为 Mod 进行下载")
             Log("[Download] OptiFine 将作为 Mod 进行下载")
-            OptiFineFolder = ModsFolder
+            OptiFineFolder = ModsTempFolder
         End If
         End If
 
 
         '记录日志
         '记录日志
@@ -2118,57 +2117,71 @@ Retry:
         LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() WriteFile(VersionFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False, .Block = False})
         LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() WriteFile(VersionFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False, .Block = False})
         'Fabric API
         'Fabric API
         If Request.FabricApi IsNot Nothing Then
         If Request.FabricApi IsNot Nothing Then
-            LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(ModsFolder)}) With {.ProgressWeight = 3, .Block = False})
+            LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(ModsTempFolder)}) With {.ProgressWeight = 3, .Block = False})
         End If
         End If
         'OptiFabric
         'OptiFabric
         If Request.OptiFabric IsNot Nothing Then
         If Request.OptiFabric IsNot Nothing Then
-            LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsFolder)}) With {.ProgressWeight = 3, .Block = False})
+            LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsTempFolder)}) With {.ProgressWeight = 3, .Block = False})
         End If
         End If
         '原版
         '原版
-        Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39, .Block = Request.ForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}
+        Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39,
+            .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}
         LoaderList.Add(ClientLoader)
         LoaderList.Add(ClientLoader)
         'OptiFine
         'OptiFine
         If Request.OptiFineEntry IsNot Nothing Then
         If Request.OptiFineEntry IsNot Nothing Then
             If OptiFineAsMod Then
             If OptiFineAsMod Then
-                LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.NameFile)) With {.Show = False, .ProgressWeight = 16, .Block = Request.ForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
+                LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.NameFile)) With {.Show = False, .ProgressWeight = 16,
+                    .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
             Else
             Else
-                LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 24, .Block = Request.ForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
+                LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 24,
+                    .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
             End If
             End If
         End If
         End If
         'Forge
         'Forge
         If Request.ForgeVersion IsNot Nothing Then
         If Request.ForgeVersion IsNot Nothing Then
-            LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgelikeLoader(False, Request.ForgeVersion, "forge-" & Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25, .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing AndAlso Request.NeoForgeEntry Is Nothing})
+            LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgelikeLoader(False, Request.ForgeVersion, "forge-" & Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25,
+                .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing AndAlso Request.NeoForgeEntry Is Nothing})
         End If
         End If
         'NeoForge
         'NeoForge
         If Request.NeoForgeVersion IsNot Nothing Then
         If Request.NeoForgeVersion IsNot Nothing Then
-            LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & Request.NeoForgeVersion, McDownloadForgelikeLoader(True, Request.NeoForgeVersion, "neoforge-" & Request.NeoForgeVersion, Request.MinecraftName, Request.NeoForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25, .Block = Request.ForgeEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
+            LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & Request.NeoForgeVersion, McDownloadForgelikeLoader(True, Request.NeoForgeVersion, "neoforge-" & Request.NeoForgeVersion, Request.MinecraftName, Request.NeoForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25,
+                .Block = Request.ForgeEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing})
         End If
         End If
         'LiteLoader
         'LiteLoader
         If Request.LiteLoaderEntry IsNot Nothing Then
         If Request.LiteLoaderEntry IsNot Nothing Then
-            LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.MinecraftName, McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 1, .Block = Request.FabricVersion Is Nothing})
+            LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.MinecraftName, McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 1,
+                .Block = Request.FabricVersion Is Nothing})
         End If
         End If
         'Fabric
         'Fabric
         If Request.FabricVersion IsNot Nothing Then
         If Request.FabricVersion IsNot Nothing Then
-            LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & Request.FabricVersion, McDownloadFabricLoader(Request.FabricVersion, Request.MinecraftName, TempMcFolder, False)) With {.Show = False, .ProgressWeight = 2, .Block = True})
+            LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & Request.FabricVersion, McDownloadFabricLoader(Request.FabricVersion, Request.MinecraftName, TempMcFolder, False)) With {.Show = False, .ProgressWeight = 2,
+                .Block = True})
         End If
         End If
         '合并安装
         '合并安装
         LoaderList.Add(New LoaderTask(Of String, String)("安装游戏",
         LoaderList.Add(New LoaderTask(Of String, String)("安装游戏",
-            Sub(Task As LoaderTask(Of String, String))
-                InstallMerge(VersionFolder, VersionFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder)
-                Task.Progress = 0.3
-                If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries")
-                If Directory.Exists(TempMcFolder & "mods") Then CopyDirectory(TempMcFolder & "mods", ModsFolder)
-                '新建 mods 文件夹
-                If Request.ForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing Then
-                    Directory.CreateDirectory(ModsFolder)
-                    Log("[Download] 自动创建 mods 文件夹:" & ModsFolder)
-                End If
-            End Sub) With {.ProgressWeight = 2, .Block = True})
+        Sub(Task As LoaderTask(Of String, String))
+            '合并 JSON
+            MergeJson(VersionFolder, VersionFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder)
+            Task.Progress = 0.2
+            '迁移文件
+            If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries")
+            Task.Progress = 0.8
+            '创建 Mod 和资源包文件夹
+            Dim ModsFolder = New McVersion(VersionFolder).PathIndie & "mods\" '版本隔离信息在此时被决定
+            If Directory.Exists(ModsTempFolder) Then
+                CopyDirectory(ModsTempFolder, ModsFolder)
+            ElseIf Modable Then
+                Directory.CreateDirectory(ModsFolder)
+                Log("[Download] 自动创建 Mod 文件夹:" & ModsFolder)
+            End If
+            Dim ResourcepacksFolder = New McVersion(VersionFolder).PathIndie & "resourcepacks\"
+            Directory.CreateDirectory(ResourcepacksFolder)
+            Log("[Download] 自动创建资源包文件夹:" & ResourcepacksFolder)
+        End Sub) With {.ProgressWeight = 2, .Block = True})
         '补全文件
         '补全文件
-        If Not DontFixLibraries AndAlso
-        (Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.BeforeFirst(".") >= 20) OrElse Request.NeoForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing) Then
+        If Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.BeforeFirst(".") >= 20) OrElse Request.NeoForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing Then
             Dim LoadersLib As New List(Of LoaderBase)
             Dim LoadersLib As New List(Of LoaderBase)
-            LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False})
+            LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False})
             LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
             LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False})
             LoaderList.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
             LoaderList.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8})
         End If
         End If
@@ -2179,9 +2192,9 @@ Retry:
     End Function
     End Function
 
 
     ''' <summary>
     ''' <summary>
-    ''' 将多个版本 Json 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。
+    ''' 将多个版本 JSON 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。
     ''' </summary>
     ''' </summary>
-    Private Sub InstallMerge(OutputFolder As String, MinecraftFolder As String, Optional OptiFineFolder As String = Nothing, Optional OptiFineAsMod As Boolean = False, Optional ForgeFolder As String = Nothing, Optional ForgeVersion As String = Nothing, Optional NeoForgeFolder As String = Nothing, Optional NeoForgeVersion As String = Nothing, Optional FabricFolder As String = Nothing, Optional LiteLoaderFolder As String = Nothing)
+    Private Sub MergeJson(OutputFolder As String, MinecraftFolder As String, Optional OptiFineFolder As String = Nothing, Optional OptiFineAsMod As Boolean = False, Optional ForgeFolder As String = Nothing, Optional ForgeVersion As String = Nothing, Optional NeoForgeFolder As String = Nothing, Optional NeoForgeVersion As String = Nothing, Optional FabricFolder As String = Nothing, Optional LiteLoaderFolder As String = Nothing)
         Log("[Download] 开始进行版本合并,输出:" & OutputFolder & ",Minecraft:" & MinecraftFolder &
         Log("[Download] 开始进行版本合并,输出:" & OutputFolder & ",Minecraft:" & MinecraftFolder &
             If(OptiFineFolder IsNot Nothing, ",OptiFine:" & OptiFineFolder, "") &
             If(OptiFineFolder IsNot Nothing, ",OptiFine:" & OptiFineFolder, "") &
             If(ForgeFolder IsNot Nothing, ",Forge:" & ForgeFolder, "") &
             If(ForgeFolder IsNot Nothing, ",Forge:" & ForgeFolder, "") &

+ 0 - 31
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml

@@ -1,31 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-    mc:Ignorable="d" x:Class="PageDownloadClient"
-    PanScroll="{Binding ElementName=PanBack}">
-    <Grid>
-        <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-            <StackPanel Orientation="Vertical" Margin="5">
-                <!--<local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="自动安装提示" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Margin="0,0,0,2" Text="本页只提供需要在下载后手动进行安装的游戏 jar 与 json 下载。" TextWrapping="Wrap" />
-                    <TextBlock Margin="0,0,0,11" Text="如果你想要进行自动安装,在游戏安装页选择游戏版本,然后直接安装即可。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                        <local:MyButton x:Name="BtnInstall" Grid.Column="1" MinWidth="140" Text="转到游戏安装页" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>-->
-                <StackPanel Name="PanMain" Margin="20,20,20,5" Grid.IsSharedSizeScope="True" />
-            </StackPanel>
-        </local:MyScrollViewer>
-        <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" Margin="40,0" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
-            <local:MyLoading Text="正在获取版本列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-        </local:MyCard>
-    </Grid>
-</local:MyPageRight>

+ 0 - 111
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb

@@ -1,111 +0,0 @@
-Public Class PageDownloadClient
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanBack, Nothing, DlClientListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '归类
-            Dim Dict As New Dictionary(Of String, List(Of JObject)) From {
-                {"正式版", New List(Of JObject)}, {"预览版", New List(Of JObject)}, {"远古版", New List(Of JObject)}, {"愚人节版", New List(Of JObject)}
-            }
-            Dim Versions As JArray = DlClientListLoader.Output.Value("versions")
-            For Each Version As JObject In Versions
-                '确定分类
-                Dim Type As String = Version("type")
-                Select Case Type
-                    Case "release"
-                        Type = "正式版"
-                    Case "snapshot"
-                        Type = "预览版"
-                        'Mojang 误分类
-                        If Version("id").ToString.StartsWith("1.") AndAlso
-                            Not Version("id").ToString.ToLower.Contains("combat") AndAlso
-                            Not Version("id").ToString.ToLower.Contains("rc") AndAlso
-                            Not Version("id").ToString.ToLower.Contains("experimental") AndAlso
-                            Not Version("id").ToString.ToLower.Contains("pre") Then
-                            Type = "正式版"
-                            Version("type") = "release"
-                        End If
-                        '愚人节版本
-                        Select Case Version("id").ToString.ToLower
-                            Case "20w14infinite", "20w14∞"
-                                Type = "愚人节版"
-                                Version("id") = "20w14∞"
-                                Version("type") = "special"
-                                Version.Add("lore", GetMcFoolName(Version("id")))
-                            Case "3d shareware v1.34", "1.rv-pre1", "15w14a", "2.0", "22w13oneblockatatime", "23w13a_or_b", "24w14potato"
-                                Type = "愚人节版"
-                                Version("type") = "special"
-                                Version.Add("lore", GetMcFoolName(Version("id")))
-                            Case Else '4/1 自动视作愚人节版
-                                Dim ReleaseDate = Version("releaseTime").Value(Of Date).ToUniversalTime().AddHours(2)
-                                If ReleaseDate.Month = 4 AndAlso ReleaseDate.Day = 1 Then
-                                    Type = "愚人节版"
-                                    Version("type") = "special"
-                                End If
-                        End Select
-                    Case "special"
-                        '已被处理的愚人节版
-                        Type = "愚人节版"
-                    Case Else
-                        Type = "远古版"
-                End Select
-                '加入辞典
-                Dict(Type).Add(Version)
-            Next
-            '排序
-            For i = 0 To Dict.Keys.Count - 1
-                Dict(Dict.Keys(i)) = Dict.Values(i).OrderByDescending(Function(v) v("releaseTime").Value(Of Date)).ToList
-            Next
-            '清空当前
-            PanMain.Children.Clear()
-            '添加最新版本
-            Dim CardInfo As New MyCard With {.Title = "最新版本", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 2}
-            Dim TopestVersions As New List(Of JObject)
-            Dim Release As JObject = Dict("正式版")(0).DeepClone()
-            Release("lore") = "最新正式版,发布于 " & Release("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm")
-            TopestVersions.Add(Release)
-            If Dict("正式版")(0)("releaseTime").Value(Of Date) < Dict("预览版")(0)("releaseTime").Value(Of Date) Then
-                Dim Snapshot As JObject = Dict("预览版")(0).DeepClone()
-                Snapshot("lore") = "最新预览版,发布于 " & Snapshot("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm")
-                TopestVersions.Add(Snapshot)
-            End If
-            Dim PanInfo As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = TopestVersions}
-            MyCard.StackInstall(PanInfo, 2)
-            CardInfo.Children.Add(PanInfo)
-            PanMain.Children.Add(CardInfo)
-            '添加其他版本
-            For Each Pair As KeyValuePair(Of String, List(Of JObject)) In Dict
-                If Not Pair.Value.Any() Then Continue For
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 2}
-                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
-                PanMain.Children.Add(NewCard)
-            Next
-        Catch ex As Exception
-            Log(ex, "可视化 MC 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    Public Sub DownloadStart(sender As MyListItem, e As Object)
-        McDownloadClient(NetPreDownloadBehaviour.HintWhileExists, sender.Title, sender.Tag("url").ToString)
-    End Sub
-
-    ''介绍栏
-    'Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-    '    OpenWebsite("https://www.minecraft.net/zh-hans")
-    'End Sub
-    'Private Sub BtnInstall_Click(sender As Object, e As EventArgs) Handles BtnInstall.Click
-    '    FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall)
-    'End Sub
-
-End Class

+ 0 - 28
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml

@@ -1,28 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-    mc:Ignorable="d" x:Class="PageDownloadFabric"
-    PanScroll="{Binding ElementName=PanBack}">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="Fabric 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Margin="0,0,0,11" Text="Fabric Loader 是新版 Minecraft 下的轻量化 Mod 加载器,你需要先安装它才能安装各种 Fabric 模组。&#xa;本页面提供 Fabric 安装器下载,在下载后你需要手动打开安装器进行安装。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <local:MyCard x:Name="CardVersions" Margin="20,15,20,20" Title="版本列表">
-                <StackPanel x:Name="PanVersions" Margin="20,40,18,20" />
-            </local:MyCard>
-            <local:MyCard HorizontalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
-                <local:MyLoading Text="正在获取 Fabric 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

+ 0 - 32
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb

@@ -1,32 +0,0 @@
-Public Class PageDownloadFabric
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, CardVersions, CardTip, DlFabricListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            Dim Versions As JArray = DlFabricListLoader.Output.Value("installer")
-            PanVersions.Children.Clear()
-            For Each Version In Versions
-                PanVersions.Children.Add(FabricDownloadListItem(Version, AddressOf Fabric_Selected))
-            Next
-            CardVersions.Title = "版本列表 (" & Versions.Count & ")"
-        Catch ex As Exception
-            Log(ex, "可视化 Fabric 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    Private Sub Fabric_Selected(sender As MyListItem, e As EventArgs)
-        McDownloadFabricLoaderSave(sender.Tag)
-    End Sub
-
-    Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-        OpenWebsite("https://www.fabricmc.net")
-    End Sub
-
-End Class

+ 0 - 26
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml

@@ -1,26 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-    mc:Ignorable="d" x:Class="PageDownloadForge"
-    PanScroll="{Binding ElementName=PanBack}">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="Forge 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Name="LabConnect" Margin="0,0,0,11" Text="Forge 是一个 Mod 加载器,你需要先安装 Forge 才能安装各种 Forge 模组。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
-            <local:MyCard HorizontalAlignment="Center" Margin="40,50" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
-                <local:MyLoading Text="正在获取 Forge 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

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

@@ -1,61 +0,0 @@
-Public Class PageDownloadForge
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlForgeListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '清空当前
-            PanMain.Children.Clear()
-            '转化为 UI
-            For Each Version As String In DlForgeListLoader.Output.Value.Sort(AddressOf VersionSortBoolean)
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Version.Replace("_p", " P"), .Margin = New Thickness(0, 0, 0, 15), .SwapType = 5}
-                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
-                PanMain.Children.Add(NewCard)
-            Next
-            ''非官方源警示
-            'If Setup.Get("ToolDownloadOutOfDate") AndAlso Not DlForgeListLoader.Output.IsOfficial Then
-            '    Dim CardWarn As New MyCard With {.Title = "过期提示", .Margin = New Thickness(0, 0, 0, 15)}
-            '    CardWarn.Children.Add(New TextBlock With {
-            '                          .Margin = New Thickness(25, MyCard.SwapedHeight, 15, 15), .VerticalAlignment = VerticalAlignment.Top, .HorizontalAlignment = HorizontalAlignment.Left, .TextTrimming = TextTrimming.None, .TextWrapping = TextWrapping.Wrap,
-            '                          .Text = "获取官方源失败,正在使用 " & DlForgeListLoader.Output.SourceName & " 镜像源,版本列表可能并非最新。" & vbCrLf & "官方源错误原因:" & If(DlForgeListLoader.Output.OfficialError, New Exception("连接服务器超时")).Message})
-            '    PanMain.Children.Insert(0, CardWarn)
-            'End If
-        Catch ex As Exception
-            Log(ex, "可视化 Forge 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    'Forge 版本列表加载
-    Public Sub Forge_Click(sender As MyLoading, e As MouseButtonEventArgs)
-        If sender.State.LoadingState = MyLoading.MyLoadingState.Error Then
-            CType(sender.State, LoaderTask(Of String, List(Of DlForgeVersionEntry))).Start(IsForceRestart:=True)
-        End If
-    End Sub
-    Public Sub Forge_StateChanged(sender As MyLoading, newState As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState)
-        If newState <> MyLoading.MyLoadingState.Stop Then Exit Sub
-
-        Dim Card As MyCard = CType(sender.Parent, FrameworkElement).Parent
-        Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = sender.State
-        '载入列表
-        Card.SwapControl.Children.Clear()
-        Card.SwapControl.Tag = Loader.Output
-        Card.SwapType = 6
-        Card.StackInstall()
-    End Sub
-
-    '介绍栏
-    Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-        OpenWebsite("https://files.minecraftforge.net")
-    End Sub
-
-End Class

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

@@ -3,7 +3,7 @@
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     mc:Ignorable="d" x:Class="PageDownloadInstall"
     mc:Ignorable="d" x:Class="PageDownloadInstall"
-    PanScroll="{Binding ElementName=PanBack}">
+    PanScroll="PanBack">
     <Grid>
     <Grid>
         <Grid Name="PanAllBack">
         <Grid Name="PanAllBack">
             <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
             <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
@@ -23,11 +23,11 @@
                             <local:MyImage x:Name="ImgLogo" Margin="52,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" Height="32" RenderOptions.BitmapScalingMode="HighQuality" />
                             <local:MyImage x:Name="ImgLogo" Margin="52,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" Height="32" RenderOptions.BitmapScalingMode="HighQuality" />
                             <local:MyTextBox Margin="93,0,16,0" Padding="-1,0" MinHeight="30" FontSize="15" VerticalAlignment="Center" x:Name="TextSelectName" MaxLength="70" HasBackground="False" />
                             <local:MyTextBox Margin="93,0,16,0" Padding="-1,0" MinHeight="30" FontSize="15" VerticalAlignment="Center" x:Name="TextSelectName" MaxLength="70" HasBackground="False" />
                         </local:MyCard>
                         </local:MyCard>
-                        <local:MyHint Text="如果不安装 Fabric API,大多数 Mod 都会无法使用!" Margin="0,10,0,0" x:Name="HintFabricAPI" IsWarn="True" />
-                        <local:MyHint Text="必须安装 OptiFabric 才能正常使用 OptiFine!" Margin="0,10,0,0" x:Name="HintOptiFabric" IsWarn="True" />
-                        <local:MyHint Text="安装结束后,请在 Mod 下载中搜索 OptiFabric Origins 并下载,否则 OptiFine 会无法使用!" Margin="0,10,0,0" x:Name="HintOptiFabricOld" IsWarn="False" />
-                        <local:MyHint Text="OptiFine 与一部分 Mod 的兼容性不佳,请谨慎安装。" Margin="0,10,0,0" x:Name="HintModOptiFine" IsWarn="False" />
-                        <local:MyCard Title="Forge" Height="40" Margin="0,12,0,0" x:Name="CardForge" IsSwaped="True" CanSwap="True" SwapLogoRight="True">
+                        <local:MyHint Text="如果不安装 Fabric API,大多数 Mod 都会无法使用!" Margin="0,10,0,0" x:Name="HintFabricAPI" Theme="Red" />
+                        <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" IsSwapped="True" CanSwap="True" SwapLogoRight="True">
                         <StackPanel Margin="20,40,18,15" VerticalAlignment="Top" Name="PanForge" />
                         <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 x:Name="PanForgeInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
                             <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" />
                                 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>
                         </Grid>
                     </local:MyCard>
                     </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" />
                         <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 x:Name="PanNeoForgeInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
                             <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" />
                           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>
                         </Grid>
                     </local:MyCard>
                     </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" />
                         <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 x:Name="PanFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
                             <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" />
                                 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>
                         </Grid>
                     </local:MyCard>
                     </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" />
                         <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 x:Name="PanFabricApiInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
                             <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" />
                                 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>
                         </Grid>
                     </local:MyCard>
                     </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 Margin="20,40,18,15" VerticalAlignment="Top">
                             <StackPanel Name="PanOptiFine" />
                             <StackPanel Name="PanOptiFine" />
                         </StackPanel>
                         </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" />
                                 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>
                         </Grid>
                     </local:MyCard>
                     </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" />
                         <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 x:Name="PanOptiFabricInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
                             <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" />
                                 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>
                         </Grid>
                     </local:MyCard>
                     </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" />
                         <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 x:Name="PanLiteLoaderInfo" Height="18" Margin="132,11,15,0" VerticalAlignment="Top" Tag="True">
                             <Grid.RenderTransform>
                             <Grid.RenderTransform>

+ 121 - 116
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb

@@ -19,7 +19,7 @@
         SelectReload()
         SelectReload()
 
 
         '非重复加载部分
         '非重复加载部分
-        If IsLoad Then Exit Sub
+        If IsLoad Then Return
         IsLoad = True
         IsLoad = True
 
 
         McDownloadForgeRecommendedRefresh()
         McDownloadForgeRecommendedRefresh()
@@ -38,7 +38,7 @@
     Public IsInSelectPage As Boolean = False
     Public IsInSelectPage As Boolean = False
     Private IsFirstLoaded As Boolean = False
     Private IsFirstLoaded As Boolean = False
     Private Sub EnterSelectPage()
     Private Sub EnterSelectPage()
-        If IsInSelectPage Then Exit Sub
+        If IsInSelectPage Then Return
         IsInSelectPage = True
         IsInSelectPage = True
 
 
         PanInner.Margin = New Thickness(25, 10, 25, 40)
         PanInner.Margin = New Thickness(25, 10, 25, 40)
@@ -54,18 +54,13 @@
 
 
         DisabledPageAnimControls.Remove(BtnStart)
         DisabledPageAnimControls.Remove(BtnStart)
         BtnStart.Show = True
         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)
         For Each Control In GetAllAnimControls(PanSelect)
@@ -87,8 +82,8 @@
         DlOptiFabricLoader.Start()
         DlOptiFabricLoader.Start()
 
 
         AniStart({
         AniStart({
-            AaOpacity(PanMinecraft, -PanMinecraft.Opacity, 90, 10),
-            AaTranslateX(PanMinecraft, -50 - CType(PanMinecraft.RenderTransform, TranslateTransform).X, 100, 10),
+            AaOpacity(PanMinecraft, -PanMinecraft.Opacity, 70, 10),
+            AaTranslateX(PanMinecraft, -50 - CType(PanMinecraft.RenderTransform, TranslateTransform).X, 90, 10),
             AaCode(
             AaCode(
             Sub()
             Sub()
                 PanBack.ScrollToHome()
                 PanBack.ScrollToHome()
@@ -101,15 +96,15 @@
                 FabricApi_Loaded()
                 FabricApi_Loaded()
                 OptiFabric_Loaded()
                 OptiFabric_Loaded()
                 SelectReload()
                 SelectReload()
+                PanMinecraft.Visibility = Visibility.Collapsed
             End Sub, After:=True),
             End Sub, After:=True),
-            AaOpacity(PanSelect, 1 - PanSelect.Opacity, 90, 100),
-            AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 200, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)),
+            AaOpacity(PanSelect, 1 - PanSelect.Opacity, 70, 100),
+            AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 160, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)),
             AaCode(
             AaCode(
             Sub()
             Sub()
-                PanMinecraft.Visibility = Visibility.Collapsed
                 PanBack.IsHitTestVisible = True
                 PanBack.IsHitTestVisible = True
                 '初始化 Binding
                 '初始化 Binding
-                If IsFirstLoaded Then Exit Sub
+                If IsFirstLoaded Then Return
                 IsFirstLoaded = True
                 IsFirstLoaded = True
                 BtnOptiFineClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFine.MainTextBlock, .Mode = BindingMode.OneWay})
                 BtnOptiFineClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFine.MainTextBlock, .Mode = BindingMode.OneWay})
                 BtnLiteLoaderClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardLiteLoader.MainTextBlock, .Mode = BindingMode.OneWay})
                 BtnLiteLoaderClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardLiteLoader.MainTextBlock, .Mode = BindingMode.OneWay})
@@ -122,7 +117,7 @@
         }, "FrmDownloadInstall SelectPageSwitch", True)
         }, "FrmDownloadInstall SelectPageSwitch", True)
     End Sub
     End Sub
     Public Sub ExitSelectPage() Handles BtnBack.Click
     Public Sub ExitSelectPage() Handles BtnBack.Click
-        If Not IsInSelectPage Then Exit Sub
+        If Not IsInSelectPage Then Return
         IsInSelectPage = False
         IsInSelectPage = False
 
 
         PanInner.Margin = New Thickness(25, 10, 25, 25)
         PanInner.Margin = New Thickness(25, 10, 25, 25)
@@ -137,11 +132,11 @@
         PanBack.ScrollToHome()
         PanBack.ScrollToHome()
 
 
         AniStart({
         AniStart({
-            AaOpacity(PanSelect, -PanSelect.Opacity, 90, 10),
-            AaTranslateX(PanSelect, 50 - CType(PanSelect.RenderTransform, TranslateTransform).X, 100, 10),
+            AaOpacity(PanSelect, -PanSelect.Opacity, 70, 10),
+            AaTranslateX(PanSelect, 50 - CType(PanSelect.RenderTransform, TranslateTransform).X, 90, 10),
             AaCode(Sub() PanBack.ScrollToHome(), After:=True),
             AaCode(Sub() PanBack.ScrollToHome(), After:=True),
-            AaOpacity(PanMinecraft, 1 - PanMinecraft.Opacity, 90, 100),
-            AaTranslateX(PanMinecraft, -CType(PanMinecraft.RenderTransform, TranslateTransform).X, 200, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)),
+            AaOpacity(PanMinecraft, 1 - PanMinecraft.Opacity, 70, 100),
+            AaTranslateX(PanMinecraft, -CType(PanMinecraft.RenderTransform, TranslateTransform).X, 160, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)),
             AaCode(
             AaCode(
             Sub()
             Sub()
                 PanSelect.Visibility = Visibility.Collapsed
                 PanSelect.Visibility = Visibility.Collapsed
@@ -168,19 +163,19 @@
     'OptiFine
     'OptiFine
     Private SelectedOptiFine As DlOptiFineListEntry = Nothing
     Private SelectedOptiFine As DlOptiFineListEntry = Nothing
     Private Sub SetOptiFineInfoShow(IsShow As String)
     Private Sub SetOptiFineInfoShow(IsShow As String)
-        If PanOptiFineInfo.Tag = IsShow Then Exit Sub
+        If PanOptiFineInfo.Tag = IsShow Then Return
         PanOptiFineInfo.Tag = IsShow
         PanOptiFineInfo.Tag = IsShow
         If IsShow = "True" Then
         If IsShow = "True" Then
             '显示信息栏
             '显示信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanOptiFineInfo, -CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack),
-                AaOpacity(PanOptiFineInfo, 1 - PanOptiFineInfo.Opacity, 100, 90)
+                AaTranslateY(PanOptiFineInfo, -CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent),
+                AaOpacity(PanOptiFineInfo, 1 - PanOptiFineInfo.Opacity, 80, 90)
             }, "SetOptiFineInfoShow")
             }, "SetOptiFineInfoShow")
         Else
         Else
             '隐藏信息栏
             '隐藏信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanOptiFineInfo, 6 - CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 200),
-                AaOpacity(PanOptiFineInfo, -PanOptiFineInfo.Opacity, 100)
+                AaTranslateY(PanOptiFineInfo, 6 - CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 120),
+                AaOpacity(PanOptiFineInfo, -PanOptiFineInfo.Opacity, 50)
             }, "SetOptiFineInfoShow")
             }, "SetOptiFineInfoShow")
         End If
         End If
     End Sub
     End Sub
@@ -188,19 +183,19 @@
     'LiteLoader
     'LiteLoader
     Private SelectedLiteLoader As DlLiteLoaderListEntry = Nothing
     Private SelectedLiteLoader As DlLiteLoaderListEntry = Nothing
     Private Sub SetLiteLoaderInfoShow(IsShow As String)
     Private Sub SetLiteLoaderInfoShow(IsShow As String)
-        If PanLiteLoaderInfo.Tag = IsShow Then Exit Sub
+        If PanLiteLoaderInfo.Tag = IsShow Then Return
         PanLiteLoaderInfo.Tag = IsShow
         PanLiteLoaderInfo.Tag = IsShow
         If IsShow = "True" Then
         If IsShow = "True" Then
             '显示信息栏
             '显示信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanLiteLoaderInfo, -CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack),
-                AaOpacity(PanLiteLoaderInfo, 1 - PanLiteLoaderInfo.Opacity, 100, 90)
+                AaTranslateY(PanLiteLoaderInfo, -CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent),
+                AaOpacity(PanLiteLoaderInfo, 1 - PanLiteLoaderInfo.Opacity, 80, 90)
             }, "SetLiteLoaderInfoShow")
             }, "SetLiteLoaderInfoShow")
         Else
         Else
             '隐藏信息栏
             '隐藏信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanLiteLoaderInfo, 6 - CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 200),
-                AaOpacity(PanLiteLoaderInfo, -PanLiteLoaderInfo.Opacity, 100)
+                AaTranslateY(PanLiteLoaderInfo, 6 - CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 120),
+                AaOpacity(PanLiteLoaderInfo, -PanLiteLoaderInfo.Opacity, 50)
             }, "SetLiteLoaderInfoShow")
             }, "SetLiteLoaderInfoShow")
         End If
         End If
     End Sub
     End Sub
@@ -208,19 +203,19 @@
     'Forge
     'Forge
     Private SelectedForge As DlForgeVersionEntry = Nothing
     Private SelectedForge As DlForgeVersionEntry = Nothing
     Private Sub SetForgeInfoShow(IsShow As String)
     Private Sub SetForgeInfoShow(IsShow As String)
-        If PanForgeInfo.Tag = IsShow Then Exit Sub
+        If PanForgeInfo.Tag = IsShow Then Return
         PanForgeInfo.Tag = IsShow
         PanForgeInfo.Tag = IsShow
         If IsShow = "True" Then
         If IsShow = "True" Then
             '显示信息栏
             '显示信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanForgeInfo, -CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack),
-                AaOpacity(PanForgeInfo, 1 - PanForgeInfo.Opacity, 100, 90)
+                AaTranslateY(PanForgeInfo, -CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent),
+                AaOpacity(PanForgeInfo, 1 - PanForgeInfo.Opacity, 80, 90)
             }, "SetForgeInfoShow")
             }, "SetForgeInfoShow")
         Else
         Else
             '隐藏信息栏
             '隐藏信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanForgeInfo, 6 - CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 200),
-                AaOpacity(PanForgeInfo, -PanForgeInfo.Opacity, 100)
+                AaTranslateY(PanForgeInfo, 6 - CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 120),
+                AaOpacity(PanForgeInfo, -PanForgeInfo.Opacity, 50)
             }, "SetForgeInfoShow")
             }, "SetForgeInfoShow")
         End If
         End If
     End Sub
     End Sub
@@ -228,19 +223,19 @@
     'NeoForge
     'NeoForge
     Private SelectedNeoForge As DlNeoForgeListEntry = Nothing
     Private SelectedNeoForge As DlNeoForgeListEntry = Nothing
     Private Sub SetNeoForgeInfoShow(IsShow As String)
     Private Sub SetNeoForgeInfoShow(IsShow As String)
-        If PanNeoForgeInfo.Tag = IsShow Then Exit Sub
+        If PanNeoForgeInfo.Tag = IsShow Then Return
         PanNeoForgeInfo.Tag = IsShow
         PanNeoForgeInfo.Tag = IsShow
         If IsShow = "True" Then
         If IsShow = "True" Then
             '显示信息栏
             '显示信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanNeoForgeInfo, -CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack),
-                AaOpacity(PanNeoForgeInfo, 1 - PanNeoForgeInfo.Opacity, 100, 90)
+                AaTranslateY(PanNeoForgeInfo, -CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent),
+                AaOpacity(PanNeoForgeInfo, 1 - PanNeoForgeInfo.Opacity, 80, 90)
             }, "SetNeoForgeInfoShow")
             }, "SetNeoForgeInfoShow")
         Else
         Else
             '隐藏信息栏
             '隐藏信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanNeoForgeInfo, 6 - CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 200),
-                AaOpacity(PanNeoForgeInfo, -PanNeoForgeInfo.Opacity, 100)
+                AaTranslateY(PanNeoForgeInfo, 6 - CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 120),
+                AaOpacity(PanNeoForgeInfo, -PanNeoForgeInfo.Opacity, 50)
             }, "SetNeoForgeInfoShow")
             }, "SetNeoForgeInfoShow")
         End If
         End If
     End Sub
     End Sub
@@ -248,19 +243,19 @@
     'Fabric
     'Fabric
     Private SelectedFabric As String = Nothing
     Private SelectedFabric As String = Nothing
     Private Sub SetFabricInfoShow(IsShow As String)
     Private Sub SetFabricInfoShow(IsShow As String)
-        If PanFabricInfo.Tag = IsShow Then Exit Sub
+        If PanFabricInfo.Tag = IsShow Then Return
         PanFabricInfo.Tag = IsShow
         PanFabricInfo.Tag = IsShow
         If IsShow = "True" Then
         If IsShow = "True" Then
             '显示信息栏
             '显示信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanFabricInfo, -CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack),
-                AaOpacity(PanFabricInfo, 1 - PanFabricInfo.Opacity, 100, 90)
+                AaTranslateY(PanFabricInfo, -CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent),
+                AaOpacity(PanFabricInfo, 1 - PanFabricInfo.Opacity, 80, 90)
             }, "SetFabricInfoShow")
             }, "SetFabricInfoShow")
         Else
         Else
             '隐藏信息栏
             '隐藏信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanFabricInfo, 6 - CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 200),
-                AaOpacity(PanFabricInfo, -PanFabricInfo.Opacity, 100)
+                AaTranslateY(PanFabricInfo, 6 - CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 120),
+                AaOpacity(PanFabricInfo, -PanFabricInfo.Opacity, 50)
             }, "SetFabricInfoShow")
             }, "SetFabricInfoShow")
         End If
         End If
     End Sub
     End Sub
@@ -268,19 +263,19 @@
     'FabricApi
     'FabricApi
     Private SelectedFabricApi As CompFile = Nothing
     Private SelectedFabricApi As CompFile = Nothing
     Private Sub SetFabricApiInfoShow(IsShow As String)
     Private Sub SetFabricApiInfoShow(IsShow As String)
-        If PanFabricApiInfo.Tag = IsShow Then Exit Sub
+        If PanFabricApiInfo.Tag = IsShow Then Return
         PanFabricApiInfo.Tag = IsShow
         PanFabricApiInfo.Tag = IsShow
         If IsShow = "True" Then
         If IsShow = "True" Then
             '显示信息栏
             '显示信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanFabricApiInfo, -CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack),
-                AaOpacity(PanFabricApiInfo, 1 - PanFabricApiInfo.Opacity, 100, 90)
+                AaTranslateY(PanFabricApiInfo, -CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent),
+                AaOpacity(PanFabricApiInfo, 1 - PanFabricApiInfo.Opacity, 80, 90)
             }, "SetFabricApiInfoShow")
             }, "SetFabricApiInfoShow")
         Else
         Else
             '隐藏信息栏
             '隐藏信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanFabricApiInfo, 6 - CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 200),
-                AaOpacity(PanFabricApiInfo, -PanFabricApiInfo.Opacity, 100)
+                AaTranslateY(PanFabricApiInfo, 6 - CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 120),
+                AaOpacity(PanFabricApiInfo, -PanFabricApiInfo.Opacity, 50)
             }, "SetFabricApiInfoShow")
             }, "SetFabricApiInfoShow")
         End If
         End If
     End Sub
     End Sub
@@ -288,19 +283,19 @@
     'OptiFabric
     'OptiFabric
     Private SelectedOptiFabric As CompFile = Nothing
     Private SelectedOptiFabric As CompFile = Nothing
     Private Sub SetOptiFabricInfoShow(IsShow As String)
     Private Sub SetOptiFabricInfoShow(IsShow As String)
-        If PanOptiFabricInfo.Tag = IsShow Then Exit Sub
+        If PanOptiFabricInfo.Tag = IsShow Then Return
         PanOptiFabricInfo.Tag = IsShow
         PanOptiFabricInfo.Tag = IsShow
         If IsShow = "True" Then
         If IsShow = "True" Then
             '显示信息栏
             '显示信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanOptiFabricInfo, -CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack),
-                AaOpacity(PanOptiFabricInfo, 1 - PanOptiFabricInfo.Opacity, 100, 90)
+                AaTranslateY(PanOptiFabricInfo, -CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent),
+                AaOpacity(PanOptiFabricInfo, 1 - PanOptiFabricInfo.Opacity, 80, 90)
             }, "SetOptiFabricInfoShow")
             }, "SetOptiFabricInfoShow")
         Else
         Else
             '隐藏信息栏
             '隐藏信息栏
             AniStart({
             AniStart({
-                AaTranslateY(PanOptiFabricInfo, 6 - CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 200),
-                AaOpacity(PanOptiFabricInfo, -PanOptiFabricInfo.Opacity, 100)
+                AaTranslateY(PanOptiFabricInfo, 6 - CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 120),
+                AaOpacity(PanOptiFabricInfo, -PanOptiFabricInfo.Opacity, 50)
             }, "SetOptiFabricInfoShow")
             }, "SetOptiFabricInfoShow")
         End If
         End If
     End Sub
     End Sub
@@ -310,7 +305,7 @@
     ''' 重载已选择的项目的显示。
     ''' 重载已选择的项目的显示。
     ''' </summary>
     ''' </summary>
     Private Sub SelectReload() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged
     Private Sub SelectReload() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged
-        If SelectedMinecraftId Is Nothing OrElse IsReloading Then Exit Sub
+        If SelectedMinecraftId Is Nothing OrElse IsReloading Then Return
         IsReloading = True
         IsReloading = True
         '主预览
         '主预览
         SelectNameUpdate()
         SelectNameUpdate()
@@ -318,8 +313,8 @@
         'OptiFine
         'OptiFine
         Dim OptiFineError As String = LoadOptiFineGetError()
         Dim OptiFineError As String = LoadOptiFineGetError()
         CardOptiFine.MainSwap.Visibility = If(OptiFineError Is Nothing, Visibility.Visible, Visibility.Collapsed)
         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
         If SelectedOptiFine Is Nothing Then
             BtnOptiFineClear.Visibility = Visibility.Collapsed
             BtnOptiFineClear.Visibility = Visibility.Collapsed
             ImgOptiFine.Visibility = Visibility.Collapsed
             ImgOptiFine.Visibility = Visibility.Collapsed
@@ -338,8 +333,8 @@
             CardLiteLoader.Visibility = Visibility.Visible
             CardLiteLoader.Visibility = Visibility.Visible
             Dim LiteLoaderError As String = LoadLiteLoaderGetError()
             Dim LiteLoaderError As String = LoadLiteLoaderGetError()
             CardLiteLoader.MainSwap.Visibility = If(LiteLoaderError Is Nothing, Visibility.Visible, Visibility.Collapsed)
             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
             If SelectedLiteLoader Is Nothing Then
                 BtnLiteLoaderClear.Visibility = Visibility.Collapsed
                 BtnLiteLoaderClear.Visibility = Visibility.Collapsed
                 ImgLiteLoader.Visibility = Visibility.Collapsed
                 ImgLiteLoader.Visibility = Visibility.Collapsed
@@ -355,8 +350,8 @@
         'Forge
         'Forge
         Dim ForgeError As String = LoadForgeGetError()
         Dim ForgeError As String = LoadForgeGetError()
         CardForge.MainSwap.Visibility = If(ForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed)
         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
         If SelectedForge Is Nothing Then
             BtnForgeClear.Visibility = Visibility.Collapsed
             BtnForgeClear.Visibility = Visibility.Collapsed
             ImgForge.Visibility = Visibility.Collapsed
             ImgForge.Visibility = Visibility.Collapsed
@@ -375,8 +370,8 @@
             CardNeoForge.Visibility = Visibility.Visible
             CardNeoForge.Visibility = Visibility.Visible
             Dim NeoForgeError As String = LoadNeoForgeGetError()
             Dim NeoForgeError As String = LoadNeoForgeGetError()
             CardNeoForge.MainSwap.Visibility = If(NeoForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed)
             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
             If SelectedNeoForge Is Nothing Then
                 BtnNeoForgeClear.Visibility = Visibility.Collapsed
                 BtnNeoForgeClear.Visibility = Visibility.Collapsed
                 ImgNeoForge.Visibility = Visibility.Collapsed
                 ImgNeoForge.Visibility = Visibility.Collapsed
@@ -396,8 +391,8 @@
             CardFabric.Visibility = Visibility.Visible
             CardFabric.Visibility = Visibility.Visible
             Dim FabricError As String = LoadFabricGetError()
             Dim FabricError As String = LoadFabricGetError()
             CardFabric.MainSwap.Visibility = If(FabricError Is Nothing, Visibility.Visible, Visibility.Collapsed)
             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
             If SelectedFabric Is Nothing Then
                 BtnFabricClear.Visibility = Visibility.Collapsed
                 BtnFabricClear.Visibility = Visibility.Collapsed
                 ImgFabric.Visibility = Visibility.Collapsed
                 ImgFabric.Visibility = Visibility.Collapsed
@@ -417,8 +412,8 @@
             CardFabricApi.Visibility = Visibility.Visible
             CardFabricApi.Visibility = Visibility.Visible
             Dim FabricApiError As String = LoadFabricApiGetError()
             Dim FabricApiError As String = LoadFabricApiGetError()
             CardFabricApi.MainSwap.Visibility = If(FabricApiError Is Nothing, Visibility.Visible, Visibility.Collapsed)
             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
             If SelectedFabricApi Is Nothing Then
                 BtnFabricApiClear.Visibility = Visibility.Collapsed
                 BtnFabricApiClear.Visibility = Visibility.Collapsed
                 ImgFabricApi.Visibility = Visibility.Collapsed
                 ImgFabricApi.Visibility = Visibility.Collapsed
@@ -438,8 +433,8 @@
             CardOptiFabric.Visibility = Visibility.Visible
             CardOptiFabric.Visibility = Visibility.Visible
             Dim OptiFabricError As String = LoadOptiFabricGetError()
             Dim OptiFabricError As String = LoadOptiFabricGetError()
             CardOptiFabric.MainSwap.Visibility = If(OptiFabricError Is Nothing, Visibility.Visible, Visibility.Collapsed)
             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
             If SelectedOptiFabric Is Nothing Then
                 BtnOptiFabricClear.Visibility = Visibility.Collapsed
                 BtnOptiFabricClear.Visibility = Visibility.Collapsed
                 ImgOptiFabric.Visibility = Visibility.Collapsed
                 ImgOptiFabric.Visibility = Visibility.Collapsed
@@ -540,13 +535,13 @@
     Private IsSelectNameEdited As Boolean = False
     Private IsSelectNameEdited As Boolean = False
     Private IsSelectNameChanging As Boolean = False
     Private IsSelectNameChanging As Boolean = False
     Private Sub SelectNameUpdate()
     Private Sub SelectNameUpdate()
-        If IsSelectNameEdited OrElse IsSelectNameChanging Then Exit Sub
+        If IsSelectNameEdited OrElse IsSelectNameChanging Then Return
         IsSelectNameChanging = True
         IsSelectNameChanging = True
         TextSelectName.Text = GetSelectName()
         TextSelectName.Text = GetSelectName()
         IsSelectNameChanging = False
         IsSelectNameChanging = False
     End Sub
     End Sub
     Private Sub TextSelectName_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextSelectName.TextChanged
     Private Sub TextSelectName_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextSelectName.TextChanged
-        If IsSelectNameChanging Then Exit Sub
+        If IsSelectNameChanging Then Return
         IsSelectNameEdited = True
         IsSelectNameEdited = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
@@ -590,7 +585,7 @@
                                 Version("id") = "20w14∞"
                                 Version("id") = "20w14∞"
                                 Version("type") = "special"
                                 Version("type") = "special"
                                 Version.Add("lore", GetMcFoolName(Version("id")))
                                 Version.Add("lore", GetMcFoolName(Version("id")))
-                            Case "3d shareware v1.34", "1.rv-pre1", "15w14a", "2.0", "22w13oneblockatatime", "23w13a_or_b", "24w14potato"
+                            Case "3d shareware v1.34", "1.rv-pre1", "15w14a", "2.0", "22w13oneblockatatime", "23w13a_or_b", "24w14potato", "25w14craftmine"
                                 Type = "愚人节版"
                                 Type = "愚人节版"
                                 Version("type") = "special"
                                 Version("type") = "special"
                                 Version.Add("lore", GetMcFoolName(Version("id")))
                                 Version.Add("lore", GetMcFoolName(Version("id")))
@@ -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}
                 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.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
                 NewCard.SwapControl = NewStack
-                NewCard.IsSwaped = True
+                NewCard.IsSwapped = True
                 PanMinecraft.Children.Add(NewCard)
                 PanMinecraft.Children.Add(NewCard)
             Next
             Next
             '自动选择版本
             '自动选择版本
@@ -676,6 +671,11 @@
             VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then
             VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then
             Return "与 Forge 不兼容"
             Return "与 Forge 不兼容"
         End If
         End If
+        '检查 Fabric 1.20.5+:全部不兼容
+        If SelectedFabric IsNot Nothing AndAlso
+            VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then
+            Return "与 Fabric 不兼容"
+        End If
         '检查 Forge 版本
         '检查 Forge 版本
         Dim HasAny As Boolean = False
         Dim HasAny As Boolean = False
         Dim HasRequiredVersion As Boolean = False
         Dim HasRequiredVersion As Boolean = False
@@ -717,7 +717,7 @@
     ''' </summary>
     ''' </summary>
     Private Sub OptiFine_Loaded() Handles LoadOptiFine.StateChanged
     Private Sub OptiFine_Loaded() Handles LoadOptiFine.StateChanged
         Try
         Try
-            If DlOptiFineListLoader.State <> LoadState.Finished Then Exit Sub
+            If DlOptiFineListLoader.State <> LoadState.Finished Then Return
 
 
             '获取版本列表
             '获取版本列表
             Dim Versions As New List(Of DlOptiFineListEntry)
             Dim Versions As New List(Of DlOptiFineListEntry)
@@ -725,7 +725,7 @@
                 If SelectedForge IsNot Nothing AndAlso Not IsOptiFineSuitForForge(Version, SelectedForge) Then Continue For
                 If SelectedForge IsNot Nothing AndAlso Not IsOptiFineSuitForForge(Version, SelectedForge) Then Continue For
                 If Version.NameDisplay.StartsWith(SelectedMinecraftId & " ") Then Versions.Add(Version)
                 If Version.NameDisplay.StartsWith(SelectedMinecraftId & " ") Then Versions.Add(Version)
             Next
             Next
-            If Not Versions.Any() Then Exit Sub
+            If Not Versions.Any() Then Return
             '排序
             '排序
             Versions = Versions.Sort(
             Versions = Versions.Sort(
             Function(Left As DlOptiFineListEntry, Right As DlOptiFineListEntry) As Boolean
             Function(Left As DlOptiFineListEntry, Right As DlOptiFineListEntry) As Boolean
@@ -750,14 +750,14 @@
         OptiFabric_Loaded()
         OptiFabric_Loaded()
         Forge_Loaded()
         Forge_Loaded()
         NeoForge_Loaded()
         NeoForge_Loaded()
-        CardOptiFine.IsSwaped = True
+        CardOptiFine.IsSwapped = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
     Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp
     Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp
         SelectedOptiFine = Nothing
         SelectedOptiFine = Nothing
         SelectedOptiFabric = Nothing
         SelectedOptiFabric = Nothing
         AutoSelectedOptiFabric = False
         AutoSelectedOptiFabric = False
-        CardOptiFine.IsSwaped = True
+        CardOptiFine.IsSwapped = True
         e.Handled = True
         e.Handled = True
         Forge_Loaded()
         Forge_Loaded()
         NeoForge_Loaded()
         NeoForge_Loaded()
@@ -791,13 +791,13 @@
     ''' </summary>
     ''' </summary>
     Private Sub LiteLoader_Loaded() Handles LoadLiteLoader.StateChanged
     Private Sub LiteLoader_Loaded() Handles LoadLiteLoader.StateChanged
         Try
         Try
-            If DlLiteLoaderListLoader.State <> LoadState.Finished Then Exit Sub
+            If DlLiteLoaderListLoader.State <> LoadState.Finished Then Return
             '获取版本列表
             '获取版本列表
             Dim Versions As New List(Of DlLiteLoaderListEntry)
             Dim Versions As New List(Of DlLiteLoaderListEntry)
             For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value
             For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value
                 If Version.Inherit = SelectedMinecraftId Then Versions.Add(Version)
                 If Version.Inherit = SelectedMinecraftId Then Versions.Add(Version)
             Next
             Next
-            If Not Versions.Any() Then Exit Sub
+            If Not Versions.Any() Then Return
             '可视化
             '可视化
             PanLiteLoader.Children.Clear()
             PanLiteLoader.Children.Clear()
             For Each Version In Versions
             For Each Version In Versions
@@ -811,12 +811,12 @@
     '选择与清除
     '选择与清除
     Private Sub LiteLoader_Selected(sender As MyListItem, e As EventArgs)
     Private Sub LiteLoader_Selected(sender As MyListItem, e As EventArgs)
         SelectedLiteLoader = sender.Tag
         SelectedLiteLoader = sender.Tag
-        CardLiteLoader.IsSwaped = True
+        CardLiteLoader.IsSwapped = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
     Private Sub LiteLoader_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnLiteLoaderClear.MouseLeftButtonUp
     Private Sub LiteLoader_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnLiteLoaderClear.MouseLeftButtonUp
         SelectedLiteLoader = Nothing
         SelectedLiteLoader = Nothing
-        CardLiteLoader.IsSwaped = True
+        CardLiteLoader.IsSwapped = True
         e.Handled = True
         e.Handled = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
@@ -871,13 +871,13 @@
     ''' </summary>
     ''' </summary>
     Private Sub Forge_Loaded() Handles LoadForge.StateChanged
     Private Sub Forge_Loaded() Handles LoadForge.StateChanged
         Try
         Try
-            If Not LoadForge.State.IsLoader Then Exit Sub
+            If Not LoadForge.State.IsLoader Then Return
             Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State
             Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State
-            If SelectedMinecraftId <> Loader.Input Then Exit Sub
-            If Loader.State <> LoadState.Finished Then Exit Sub
+            If SelectedMinecraftId <> Loader.Input Then Return
+            If Loader.State <> LoadState.Finished Then Return
             '获取要显示的版本
             '获取要显示的版本
             Dim Versions = Loader.Output.ToList '复制数组,以免 Output 在实例化后变空
             Dim Versions = Loader.Output.ToList '复制数组,以免 Output 在实例化后变空
-            If Not Loader.Output.Any() Then Exit Sub
+            If Not Loader.Output.Any() Then Return
             PanForge.Children.Clear()
             PanForge.Children.Clear()
             Versions = Versions.Where(
             Versions = Versions.Where(
             Function(v)
             Function(v)
@@ -897,14 +897,14 @@
     '选择与清除
     '选择与清除
     Private Sub Forge_Selected(sender As MyListItem, e As EventArgs)
     Private Sub Forge_Selected(sender As MyListItem, e As EventArgs)
         SelectedForge = sender.Tag
         SelectedForge = sender.Tag
-        CardForge.IsSwaped = True
+        CardForge.IsSwapped = True
         If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, SelectedForge) Then SelectedOptiFine = Nothing
         If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, SelectedForge) Then SelectedOptiFine = Nothing
         OptiFine_Loaded()
         OptiFine_Loaded()
         SelectReload()
         SelectReload()
     End Sub
     End Sub
     Private Sub Forge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnForgeClear.MouseLeftButtonUp
     Private Sub Forge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnForgeClear.MouseLeftButtonUp
         SelectedForge = Nothing
         SelectedForge = Nothing
-        CardForge.IsSwaped = True
+        CardForge.IsSwapped = True
         e.Handled = True
         e.Handled = True
         OptiFine_Loaded()
         OptiFine_Loaded()
         SelectReload()
         SelectReload()
@@ -942,9 +942,9 @@
     Private Sub NeoForge_Loaded() Handles LoadNeoForge.StateChanged
     Private Sub NeoForge_Loaded() Handles LoadNeoForge.StateChanged
         Try
         Try
             '获取版本列表
             '获取版本列表
-            If DlNeoForgeListLoader.State <> LoadState.Finished Then Exit Sub
+            If DlNeoForgeListLoader.State <> LoadState.Finished Then Return
             Dim Versions = DlNeoForgeListLoader.Output.Value.Where(Function(v) v.Inherit = SelectedMinecraftId).ToList
             Dim Versions = DlNeoForgeListLoader.Output.Value.Where(Function(v) v.Inherit = SelectedMinecraftId).ToList
-            If Not Versions.Any() Then Exit Sub
+            If Not Versions.Any() Then Return
             '可视化
             '可视化
             PanNeoForge.Children.Clear()
             PanNeoForge.Children.Clear()
             NeoForgeDownloadListItemPreload(PanNeoForge, Versions, AddressOf NeoForge_Selected, False)
             NeoForgeDownloadListItemPreload(PanNeoForge, Versions, AddressOf NeoForge_Selected, False)
@@ -959,13 +959,13 @@
     '选择与清除
     '选择与清除
     Private Sub NeoForge_Selected(sender As MyListItem, e As EventArgs)
     Private Sub NeoForge_Selected(sender As MyListItem, e As EventArgs)
         SelectedNeoForge = sender.Tag
         SelectedNeoForge = sender.Tag
-        CardNeoForge.IsSwaped = True
+        CardNeoForge.IsSwapped = True
         OptiFine_Loaded()
         OptiFine_Loaded()
         SelectReload()
         SelectReload()
     End Sub
     End Sub
     Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp
     Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp
         SelectedNeoForge = Nothing
         SelectedNeoForge = Nothing
-        CardNeoForge.IsSwaped = True
+        CardNeoForge.IsSwapped = True
         e.Handled = True
         e.Handled = True
         OptiFine_Loaded()
         OptiFine_Loaded()
         SelectReload()
         SelectReload()
@@ -981,6 +981,11 @@
     Private Function LoadFabricGetError() As String
     Private Function LoadFabricGetError() As String
         If LoadFabric Is Nothing OrElse LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
         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
         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")
         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 Version("version").ToString = SelectedMinecraftId.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") Then
                 If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容"
                 If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容"
@@ -1001,10 +1006,10 @@
     ''' </summary>
     ''' </summary>
     Private Sub Fabric_Loaded() Handles LoadFabric.StateChanged
     Private Sub Fabric_Loaded() Handles LoadFabric.StateChanged
         Try
         Try
-            If DlFabricListLoader.State <> LoadState.Finished Then Exit Sub
+            If DlFabricListLoader.State <> LoadState.Finished Then Return
             '获取版本列表
             '获取版本列表
             Dim Versions As JArray = DlFabricListLoader.Output.Value("loader")
             Dim Versions As JArray = DlFabricListLoader.Output.Value("loader")
-            If Not Versions.Any() Then Exit Sub
+            If Not Versions.Any() Then Return
             '可视化
             '可视化
             PanFabric.Children.Clear()
             PanFabric.Children.Clear()
             PanFabric.Tag = Versions
             PanFabric.Tag = Versions
@@ -1020,7 +1025,7 @@
         SelectedFabric = sender.Tag("version").ToString
         SelectedFabric = sender.Tag("version").ToString
         FabricApi_Loaded()
         FabricApi_Loaded()
         OptiFabric_Loaded()
         OptiFabric_Loaded()
-        CardFabric.IsSwaped = True
+        CardFabric.IsSwapped = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
     Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp
     Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp
@@ -1029,7 +1034,7 @@
         AutoSelectedFabricApi = False
         AutoSelectedFabricApi = False
         SelectedOptiFabric = Nothing
         SelectedOptiFabric = Nothing
         AutoSelectedOptiFabric = False
         AutoSelectedOptiFabric = False
-        CardFabric.IsSwaped = True
+        CardFabric.IsSwapped = True
         e.Handled = True
         e.Handled = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
@@ -1106,8 +1111,8 @@
     ''' </summary>
     ''' </summary>
     Private Sub FabricApi_Loaded() Handles LoadFabricApi.StateChanged
     Private Sub FabricApi_Loaded() Handles LoadFabricApi.StateChanged
         Try
         Try
-            If DlFabricApiLoader.State <> LoadState.Finished Then Exit Sub
-            If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing Then Exit Sub
+            If DlFabricApiLoader.State <> LoadState.Finished Then Return
+            If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing Then Return
             '获取版本列表
             '获取版本列表
             Dim Versions As New List(Of CompFile)
             Dim Versions As New List(Of CompFile)
             For Each Version In DlFabricApiLoader.Output
             For Each Version In DlFabricApiLoader.Output
@@ -1119,7 +1124,7 @@
                     Versions.Add(Version)
                     Versions.Add(Version)
                 End If
                 End If
             Next
             Next
-            If Not Versions.Any() Then Exit Sub
+            If Not Versions.Any() Then Return
             Versions = Versions.OrderByDescending(Function(v) v.ReleaseDate).ToList
             Versions = Versions.OrderByDescending(Function(v) v.ReleaseDate).ToList
             '可视化
             '可视化
             PanFabricApi.Children.Clear()
             PanFabricApi.Children.Clear()
@@ -1141,12 +1146,12 @@
     '选择与清除
     '选择与清除
     Private Sub FabricApi_Selected(sender As MyListItem, e As EventArgs)
     Private Sub FabricApi_Selected(sender As MyListItem, e As EventArgs)
         SelectedFabricApi = sender.Tag
         SelectedFabricApi = sender.Tag
-        CardFabricApi.IsSwaped = True
+        CardFabricApi.IsSwapped = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
     Private Sub FabricApi_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricApiClear.MouseLeftButtonUp
     Private Sub FabricApi_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricApiClear.MouseLeftButtonUp
         SelectedFabricApi = Nothing
         SelectedFabricApi = Nothing
-        CardFabricApi.IsSwaped = True
+        CardFabricApi.IsSwapped = True
         e.Handled = True
         e.Handled = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
@@ -1202,14 +1207,14 @@
     ''' </summary>
     ''' </summary>
     Private Sub OptiFabric_Loaded() Handles LoadOptiFabric.StateChanged
     Private Sub OptiFabric_Loaded() Handles LoadOptiFabric.StateChanged
         Try
         Try
-            If DlOptiFabricLoader.State <> LoadState.Finished Then Exit Sub
-            If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing OrElse SelectedOptiFine Is Nothing Then Exit Sub
+            If DlOptiFabricLoader.State <> LoadState.Finished Then Return
+            If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing OrElse SelectedOptiFine Is Nothing Then Return
             '获取版本列表
             '获取版本列表
             Dim Versions As New List(Of CompFile)
             Dim Versions As New List(Of CompFile)
             For Each Version In DlOptiFabricLoader.Output
             For Each Version In DlOptiFabricLoader.Output
                 If IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Versions.Add(Version)
                 If IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Versions.Add(Version)
             Next
             Next
-            If Not Versions.Any() Then Exit Sub
+            If Not Versions.Any() Then Return
             '排序
             '排序
             Versions = Versions.OrderByDescending(Function(v) v.ReleaseDate).ToList
             Versions = Versions.OrderByDescending(Function(v) v.ReleaseDate).ToList
             '可视化
             '可视化
@@ -1233,12 +1238,12 @@
     '选择与清除
     '选择与清除
     Private Sub OptiFabric_Selected(sender As MyListItem, e As EventArgs)
     Private Sub OptiFabric_Selected(sender As MyListItem, e As EventArgs)
         SelectedOptiFabric = sender.Tag
         SelectedOptiFabric = sender.Tag
-        CardOptiFabric.IsSwaped = True
+        CardOptiFabric.IsSwapped = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
     Private Sub OptiFabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFabricClear.MouseLeftButtonUp
     Private Sub OptiFabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFabricClear.MouseLeftButtonUp
         SelectedOptiFabric = Nothing
         SelectedOptiFabric = Nothing
-        CardOptiFabric.IsSwaped = True
+        CardOptiFabric.IsSwapped = True
         e.Handled = True
         e.Handled = True
         SelectReload()
         SelectReload()
     End Sub
     End Sub
@@ -1254,10 +1259,10 @@
         '确认版本隔离
         '确认版本隔离
         If (SelectedForge IsNot Nothing OrElse SelectedNeoForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) AndAlso
         If (SelectedForge IsNot Nothing OrElse SelectedNeoForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) AndAlso
            (Setup.Get("LaunchArgumentIndieV2") = 0 OrElse Setup.Get("LaunchArgumentIndieV2") = 2) Then
            (Setup.Get("LaunchArgumentIndieV2") = 0 OrElse Setup.Get("LaunchArgumentIndieV2") = 2) Then
-            If MyMsgBox("你尚未开启版本隔离,这会导致多个版本共用同一个 Mod 文件夹。" & vbCrLf &
-                        "因此游戏可能会因为读取到与当前版本不符的 Mod 而崩溃。" & vbCrLf &
-                        "推荐在开始下载前,在 设置 → 启动选项 → 版本隔离 中开启版本隔离!", "版本隔离提示", "取消下载", "继续") = 1 Then
-                Exit Sub
+            If MyMsgBox("你尚未开启版本隔离,多个 MC 版本会共用同一个 Mod 文件夹。" & vbCrLf &
+                        "因此,游戏可能会因为读取到与当前版本不符的 Mod 而崩溃。" & vbCrLf &
+                        "推荐先在 设置 → 启动选项 → 默认版本隔离 中开启版本隔离!", "版本隔离提示", "取消下载", "继续") = 1 Then
+                Return
             End If
             End If
         End If
         End If
         '提交安装申请
         '提交安装申请
@@ -1275,7 +1280,7 @@
             .OptiFabric = SelectedOptiFabric,
             .OptiFabric = SelectedOptiFabric,
             .LiteLoaderEntry = SelectedLiteLoader
             .LiteLoaderEntry = SelectedLiteLoader
         }
         }
-        If Not McInstall(Request) Then Exit Sub
+        If Not McInstall(Request) Then Return
         '返回,这样在再次进入安装页面时这个版本就会显示文件夹已重复
         '返回,这样在再次进入安装页面时这个版本就会显示文件夹已重复
         ExitSelectPage()
         ExitSelectPage()
     End Sub
     End Sub

Разница между файлами не показана из-за своего большого размера
+ 4 - 27
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml


+ 49 - 96
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb

@@ -11,7 +11,7 @@
     ''' <summary>
     ''' <summary>
     ''' 勾选事件改变页面。
     ''' 勾选事件改变页面。
     ''' </summary>
     ''' </summary>
-    Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemInstall.Check, ItemClient.Check, ItemOptiFine.Check, ItemForge.Check, ItemNeoForge.Check, ItemLiteLoader.Check, ItemMod.Check, ItemFabric.Check, ItemPack.Check
+    Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemInstall.Check, ItemMod.Check, ItemPack.Check, ItemResourcePack.Check, ItemShader.Check, ItemDataPack.Check
         '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0
         '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0
         '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False)
         '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False)
         If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag))
         If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag))
@@ -23,30 +23,21 @@
             Case FormMain.PageSubType.DownloadInstall
             Case FormMain.PageSubType.DownloadInstall
                 If FrmDownloadInstall Is Nothing Then FrmDownloadInstall = New PageDownloadInstall
                 If FrmDownloadInstall Is Nothing Then FrmDownloadInstall = New PageDownloadInstall
                 Return FrmDownloadInstall
                 Return FrmDownloadInstall
-            Case FormMain.PageSubType.DownloadClient
-                If FrmDownloadClient Is Nothing Then FrmDownloadClient = New PageDownloadClient
-                Return FrmDownloadClient
-            Case FormMain.PageSubType.DownloadOptiFine
-                If FrmDownloadOptiFine Is Nothing Then FrmDownloadOptiFine = New PageDownloadOptiFine
-                Return FrmDownloadOptiFine
-            Case FormMain.PageSubType.DownloadForge
-                If FrmDownloadForge Is Nothing Then FrmDownloadForge = New PageDownloadForge
-                Return FrmDownloadForge
-            Case FormMain.PageSubType.DownloadNeoForge
-                If FrmDownloadNeoForge Is Nothing Then FrmDownloadNeoForge = New PageDownloadNeoForge
-                Return FrmDownloadNeoForge
-            Case FormMain.PageSubType.DownloadLiteLoader
-                If FrmDownloadLiteLoader Is Nothing Then FrmDownloadLiteLoader = New PageDownloadLiteLoader
-                Return FrmDownloadLiteLoader
-            Case FormMain.PageSubType.DownloadFabric
-                If FrmDownloadFabric Is Nothing Then FrmDownloadFabric = New PageDownloadFabric
-                Return FrmDownloadFabric
             Case FormMain.PageSubType.DownloadMod
             Case FormMain.PageSubType.DownloadMod
                 If FrmDownloadMod Is Nothing Then FrmDownloadMod = New PageDownloadMod
                 If FrmDownloadMod Is Nothing Then FrmDownloadMod = New PageDownloadMod
                 Return FrmDownloadMod
                 Return FrmDownloadMod
             Case FormMain.PageSubType.DownloadPack
             Case FormMain.PageSubType.DownloadPack
                 If FrmDownloadPack Is Nothing Then FrmDownloadPack = New PageDownloadPack
                 If FrmDownloadPack Is Nothing Then FrmDownloadPack = New PageDownloadPack
                 Return FrmDownloadPack
                 Return FrmDownloadPack
+            Case FormMain.PageSubType.DownloadResourcePack
+                If FrmDownloadResourcePack Is Nothing Then FrmDownloadResourcePack = New PageDownloadResourcePack
+                Return FrmDownloadResourcePack
+            Case FormMain.PageSubType.DownloadShader
+                If FrmDownloadShader Is Nothing Then FrmDownloadShader = New PageDownloadShader
+                Return FrmDownloadShader
+            Case FormMain.PageSubType.DownloadDataPack
+                If FrmDownloadDataPack Is Nothing Then FrmDownloadDataPack = New PageDownloadDataPack
+                Return FrmDownloadDataPack
             Case Else
             Case Else
                 Throw New Exception("未知的下载子页面种类:" & ID)
                 Throw New Exception("未知的下载子页面种类:" & ID)
         End Select
         End Select
@@ -56,7 +47,7 @@
     ''' 切换现有页面。
     ''' 切换现有页面。
     ''' </summary>
     ''' </summary>
     Public Sub PageChange(ID As FormMain.PageSubType)
     Public Sub PageChange(ID As FormMain.PageSubType)
-        If PageID = ID Then Exit Sub
+        If PageID = ID Then Return
         AniControlEnabled += 1
         AniControlEnabled += 1
         Try
         Try
             PageChangeRun(PageGet(ID))
             PageChangeRun(PageGet(ID))
@@ -110,96 +101,58 @@
                 DlOptiFabricLoader.Start(IsForceRestart:=True)
                 DlOptiFabricLoader.Start(IsForceRestart:=True)
                 ItemInstall.Checked = True
                 ItemInstall.Checked = True
             Case FormMain.PageSubType.DownloadMod
             Case FormMain.PageSubType.DownloadMod
-                PageDownloadMod.Storage = New CompProjectStorage
-                PageDownloadMod.Page = 0
                 CompProjectCache.Clear()
                 CompProjectCache.Clear()
                 CompFilesCache.Clear()
                 CompFilesCache.Clear()
-                If FrmDownloadMod IsNot Nothing Then FrmDownloadMod.PageLoaderRestart()
+                If FrmDownloadMod IsNot Nothing Then
+                    FrmDownloadMod.Content.Storage = New CompProjectStorage
+                    FrmDownloadMod.Content.Page = 0
+                    FrmDownloadMod.PageLoaderRestart()
+                End If
                 ItemMod.Checked = True
                 ItemMod.Checked = True
             Case FormMain.PageSubType.DownloadPack
             Case FormMain.PageSubType.DownloadPack
-                PageDownloadPack.Storage = New CompProjectStorage
-                PageDownloadPack.Page = 0
                 CompProjectCache.Clear()
                 CompProjectCache.Clear()
                 CompFilesCache.Clear()
                 CompFilesCache.Clear()
-                If FrmDownloadPack IsNot Nothing Then FrmDownloadPack.PageLoaderRestart()
+                If FrmDownloadPack IsNot Nothing Then
+                    FrmDownloadPack.Content.Storage = New CompProjectStorage
+                    FrmDownloadPack.Content.Page = 0
+                    FrmDownloadPack.PageLoaderRestart()
+                End If
                 ItemPack.Checked = True
                 ItemPack.Checked = True
-            Case FormMain.PageSubType.DownloadClient
-                DlClientListLoader.Start(IsForceRestart:=True)
-                ItemClient.Checked = True
-            Case FormMain.PageSubType.DownloadOptiFine
-                DlOptiFineListLoader.Start(IsForceRestart:=True)
-                ItemOptiFine.Checked = True
-            Case FormMain.PageSubType.DownloadForge
-                DlForgeListLoader.Start(IsForceRestart:=True)
-                ItemForge.Checked = True
-            Case FormMain.PageSubType.DownloadNeoForge
-                DlNeoForgeListLoader.Start(IsForceRestart:=True)
-                ItemNeoForge.Checked = True
-            Case FormMain.PageSubType.DownloadLiteLoader
-                DlLiteLoaderListLoader.Start(IsForceRestart:=True)
-                ItemLiteLoader.Checked = True
-            Case FormMain.PageSubType.DownloadFabric
-                DlFabricListLoader.Start(IsForceRestart:=True)
-                ItemFabric.Checked = True
+            Case FormMain.PageSubType.DownloadResourcePack
+                CompProjectCache.Clear()
+                CompFilesCache.Clear()
+                If FrmDownloadResourcePack IsNot Nothing Then
+                    FrmDownloadResourcePack.Content.Storage = New CompProjectStorage
+                    FrmDownloadResourcePack.Content.Page = 0
+                    FrmDownloadResourcePack.PageLoaderRestart()
+                End If
+                ItemResourcePack.Checked = True
+            Case FormMain.PageSubType.DownloadShader
+                CompProjectCache.Clear()
+                CompFilesCache.Clear()
+                If FrmDownloadShader IsNot Nothing Then
+                    FrmDownloadShader.Content.Storage = New CompProjectStorage
+                    FrmDownloadShader.Content.Page = 0
+                    FrmDownloadShader.PageLoaderRestart()
+                End If
+                ItemShader.Checked = True
+            Case FormMain.PageSubType.DownloadDataPack
+                CompProjectCache.Clear()
+                CompFilesCache.Clear()
+                If FrmDownloadDataPack IsNot Nothing Then
+                    FrmDownloadDataPack.Content.Storage = New CompProjectStorage
+                    FrmDownloadDataPack.Content.Page = 0
+                    FrmDownloadDataPack.PageLoaderRestart()
+                End If
+                ItemDataPack.Checked = True
         End Select
         End Select
         Hint("正在刷新……", Log:=False)
         Hint("正在刷新……", Log:=False)
     End Sub
     End Sub
 
 
     '点击返回
     '点击返回
     Private Sub ItemInstall_Click(sender As Object, e As MouseButtonEventArgs) Handles ItemInstall.Click
     Private Sub ItemInstall_Click(sender As Object, e As MouseButtonEventArgs) Handles ItemInstall.Click
-        If Not ItemInstall.Checked Then Exit Sub
+        If Not ItemInstall.Checked Then Return
         FrmDownloadInstall.ExitSelectPage()
         FrmDownloadInstall.ExitSelectPage()
     End Sub
     End Sub
 
 
-    '展开手动安装
-    Private Sub ItemHand_Click(sender As Object, e As RouteEventArgs) Handles ItemHand.Changed
-        If ItemHand.Checked = False Then Exit Sub
-        e.Handled = True
-        AniControlEnabled += 1
-        If Not Setup.Get("HintHandInstall") Then
-            Setup.Set("HintHandInstall", True)
-            If MyMsgBox("手动安装包功能提供了 OptiFine、Forge 等组件的 .jar 安装文件下载,但无法自动安装。" & vbCrLf &
-                        "在自动安装页面先选择 MC 版本,然后就可以选择 OptiFine、Forge 等组件,让 PCL 自动进行安装了。", "自动安装提示", "返回自动安装", "继续下载手动安装包") = 1 Then
-                FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.Download}, FormMain.PageSubType.DownloadInstall)
-                AniControlEnabled -= 1
-                Exit Sub
-            End If
-        End If
-        ItemHand.Visibility = Visibility.Collapsed
-        LabGame.Visibility = Visibility.Collapsed
-        LabHand.Visibility = Visibility.Visible
-        ItemClient.Visibility = Visibility.Visible
-        ItemOptiFine.Visibility = Visibility.Visible
-        ItemFabric.Visibility = Visibility.Visible
-        ItemForge.Visibility = Visibility.Visible
-        ItemNeoForge.Visibility = Visibility.Visible
-        ItemLiteLoader.Visibility = Visibility.Visible
-        RunInThread(
-        Sub()
-            Thread.Sleep(20)
-            RunInUiWait(Sub() ItemClient.SetChecked(True, True, True))
-            AniControlEnabled -= 1
-        End Sub)
-    End Sub
-    '折叠手动安装
-    Private Sub LabHand_Click(sender As Object, e As MouseButtonEventArgs) Handles LabHand.MouseLeftButtonUp
-        e.Handled = True
-        AniControlEnabled += 1
-        ItemHand.Visibility = Visibility.Visible
-        LabGame.Visibility = Visibility.Visible
-        LabHand.Visibility = Visibility.Collapsed
-        ItemClient.Visibility = Visibility.Collapsed
-        ItemOptiFine.Visibility = Visibility.Collapsed
-        ItemNeoForge.Visibility = Visibility.Collapsed
-        ItemFabric.Visibility = Visibility.Collapsed
-        ItemForge.Visibility = Visibility.Collapsed
-        ItemLiteLoader.Visibility = Visibility.Collapsed
-        RunInThread(
-        Sub()
-            Thread.Sleep(20)
-            RunInUiWait(Sub() ItemInstall.SetChecked(True, True, True))
-            AniControlEnabled -= 1
-        End Sub)
-    End Sub
-
 End Class
 End Class

+ 0 - 26
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml

@@ -1,26 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-    mc:Ignorable="d" x:Class="PageDownloadLiteLoader"
-    PanScroll="{Binding ElementName=PanBack}">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="LiteLoader 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Name="LabConnect" Margin="0,0,0,11" Text="与 Forge 类似,LiteLoader 可以用于加载老版本 Minecraft 中的 LiteLoader 模组。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
-            <local:MyCard HorizontalAlignment="Center" Margin="40,50" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False">
-                <local:MyLoading Text="正在获取 LiteLoader 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

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

@@ -1,52 +0,0 @@
-Public Class PageDownloadLiteLoader
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlLiteLoaderListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '归类
-            Dim Dict As New Dictionary(Of String, List(Of DlLiteLoaderListEntry))
-            For VersionCode As Integer = 30 To 0 Step -1
-                Dict.Add("1." & VersionCode, New List(Of DlLiteLoaderListEntry))
-            Next
-            Dict.Add("未知版本", New List(Of DlLiteLoaderListEntry))
-            For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value
-                Dim MainVersion As String = "1." & Version.Inherit.Split(".")(1)
-                If Dict.ContainsKey(MainVersion) Then
-                    Dict(MainVersion).Add(Version)
-                Else
-                    Dict("未知版本").Add(Version)
-                End If
-            Next
-            '清空当前
-            PanMain.Children.Clear()
-            '转化为 UI
-            For Each Pair As KeyValuePair(Of String, List(Of DlLiteLoaderListEntry)) In Dict
-                If Not Pair.Value.Any() Then Continue For
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 10}
-                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
-                PanMain.Children.Add(NewCard)
-            Next
-        Catch ex As Exception
-            Log(ex, "可视化 LiteLoader 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    Public Sub DownloadStart(sender As MyListItem, e As Object)
-        McDownloadLiteLoader(sender.Tag)
-    End Sub
-    Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-        OpenWebsite("https://www.liteloader.com")
-    End Sub
-
-End Class

+ 0 - 121
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml

@@ -1,121 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-    mc:Ignorable="d" x:Class="PageDownloadMod"
-    PanScroll="{Binding ElementName=PanBack}">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="25">
-            <local:MyCard Title="搜索 Mod" Margin="0,0,0,15" x:Name="PanAlways">
-                <StackPanel Margin="25,40,25,15">
-                    <Grid Margin="0,0,0,11" HorizontalAlignment="Stretch">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" />
-                            <ColumnDefinition Width="1.3*" />
-                            <ColumnDefinition Width="1.2*" />
-                            <ColumnDefinition Width="50" />
-                            <ColumnDefinition Width="Auto" />
-                            <ColumnDefinition Width="1*" />
-                        </Grid.ColumnDefinitions>
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="28" />
-                            <RowDefinition Height="9" />
-                            <RowDefinition Height="28" />
-                        </Grid.RowDefinitions>
-                        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="名称" Margin="0,0,18,0" />
-                        <local:MyTextBox x:Name="TextSearchName" Grid.Column="1" Grid.ColumnSpan="2" />
-                        <TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Left" Text="来源" Margin="0,0,18,0" />
-                        <local:MyComboBox x:Name="ComboSearchSource" Grid.Column="5">
-                            <local:MyComboBoxItem Content="全部" Tag="3" IsSelected="True" />
-                            <local:MyComboBoxItem Content="CurseForge" Tag="1" />
-                            <local:MyComboBoxItem Content="Modrinth" Tag="2" />
-                        </local:MyComboBox>
-                        <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.4" />
-                            <local:MyComboBoxItem Content="1.21.1" />
-                            <local:MyComboBoxItem Content="1.20.6" />
-                            <local:MyComboBoxItem Content="1.20.1" />
-                            <local:MyComboBoxItem Content="1.19.4" />
-                            <local:MyComboBoxItem Content="1.19.2" />
-                            <local:MyComboBoxItem Content="1.18.2" />
-                            <local:MyComboBoxItem Content="1.17.1" />
-                            <local:MyComboBoxItem Content="1.16.5" />
-                            <local:MyComboBoxItem Content="1.14.4" />
-                            <local:MyComboBoxItem Content="1.12.2" />
-                            <local:MyComboBoxItem Content="1.10.2" />
-                            <local:MyComboBoxItem Content="1.8.9" />
-                            <local:MyComboBoxItem Content="1.7.10" />
-                        </local:MyComboBox>
-                        <local:MyComboBox x:Name="ComboSearchLoader" Visibility="Collapsed" Grid.Row="2" Grid.Column="2" MaxDropDownHeight="320" Margin="10,0,0,0">
-                            <local:MyComboBoxItem Content="任意 Mod 加载器" IsSelected="True" Tag="0" />
-                            <local:MyComboBoxItem Content="Forge" Tag="1" />
-                            <local:MyComboBoxItem Content="Fabric" Tag="4" />
-                            <local:MyComboBoxItem Content="Quilt" Tag="5" />
-                            <local:MyComboBoxItem Content="NeoForge" Tag="6" />
-                        </local:MyComboBox>
-                        <TextBlock VerticalAlignment="Center" Grid.Column="4" Grid.Row="2" HorizontalAlignment="Left" Text="类型" Margin="0,0,18,0" />
-                        <local:MyComboBox x:Name="ComboSearchTag" Grid.Column="5" Grid.Row="2" MaxDropDownHeight="320">
-                            <local:MyComboBoxItem Content="全部" Tag="" IsSelected="True" />
-                            <local:MyComboBoxItem Content="世界元素" Tag="406/worldgen" />
-                            <local:MyComboBoxItem Content="生物群系" Tag="407/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="维度" Tag="410/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="矿物与资源" Tag="408/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="天然结构" Tag="409/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="科技" Tag="412/technology" />
-                            <local:MyComboBoxItem Content="管道与物流" Tag="415/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="自动化" Tag="4843/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="能源" Tag="417/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="红石" Tag="4558/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="食物与烹饪" Tag="436/food" />
-                            <local:MyComboBoxItem Content="农业" Tag="416/" Margin="15,0,0,0" />
-                            <local:MyComboBoxItem Content="游戏机制" Tag="/game-mechanics" />
-                            <local:MyComboBoxItem Content="运输" Tag="414/transportation" />
-                            <local:MyComboBoxItem Content="仓储" Tag="420/storage" />
-                            <local:MyComboBoxItem Content="魔法" Tag="419/magic" />
-                            <local:MyComboBoxItem Content="冒险" Tag="422/adventure" />
-                            <local:MyComboBoxItem Content="装饰" Tag="424/decoration" />
-                            <local:MyComboBoxItem Content="生物" Tag="411/mobs" />
-                            <local:MyComboBoxItem Content="装备与工具" Tag="434/equipment" />
-                            <local:MyComboBoxItem Content="性能优化" Tag="/optimization" />
-                            <local:MyComboBoxItem Content="信息显示" Tag="423/" />
-                            <local:MyComboBoxItem Content="服务器" Tag="435/social" />
-                            <local:MyComboBoxItem Content="改良" Tag="5191/utility" />
-                            <local:MyComboBoxItem Content="支持库" Tag="421/library" />
-                        </local:MyComboBox>
-                    </Grid>
-                    <Grid Margin="0,4,0,6" Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnSearchRun" Text="搜索" MinWidth="140" Padding="13,0" Margin="0,0,20,0" ColorType="Highlight" />
-                        <local:MyButton x:Name="BtnSearchReset" Text="重置条件" MinWidth="140" Padding="13,0" Margin="0,0,20,0" Grid.Column="1" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel x:Name="PanContent">
-                <local:MyHint x:Name="HintError" Text="无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,结果可能不全。&#xa;请尝试使用 VPN 或加速器以改善网络。" IsWarn="True" Margin="0,0,0,15" />
-                <local:MyCard x:Name="CardProjects" UseAnimation="False">
-                    <StackPanel x:Name="PanProjects" Margin="12" />
-                </local:MyCard>
-                <local:MyCard Margin="0,15,0,0" x:Name="CardPages" HorizontalAlignment="Center">
-                    <StackPanel Orientation="Horizontal" Height="23" Margin="10,7">
-                        <local:MyIconButton x:Name="BtnPageFirst" IsEnabled="False" Opacity="0.2" Margin="0,0.5,5,0.5"
-                            Logo="M496.213333 329.856L315.306667 510.848l180.992 181.077333a42.666667 42.666667 0 1 1-60.330667 60.330667l-211.2-211.2a42.453333 42.453333 0 0 1-11.818667-22.613333l-0.597333-5.034667v-5.034667a42.496 42.496 0 0 1 12.373333-27.648l211.2-211.2a42.666667 42.666667 0 0 1 60.373334 60.330667z m298.666667 0l-180.949333 180.992 180.992 181.077333a42.666667 42.666667 0 1 1-60.330667 60.330667l-211.2-211.2a42.453333 42.453333 0 0 1-11.818667-22.613333l-0.597333-5.034667v-5.034667a42.496 42.496 0 0 1 12.373333-27.648l211.2-211.2a42.666667 42.666667 0 0 1 60.373334 60.330667z" />
-                        <local:MyIconButton x:Name="BtnPageLeft" IsEnabled="False" Opacity="0.2" Margin="0,0,5,0"
-                            Logo="M650.752 278.357333l-241.322667 241.365334 241.322667 241.365333a42.666667 42.666667 0 1 1-60.330667 60.330667l-271.530666-271.530667a42.453333 42.453333 0 0 1-11.818667-22.613333l-0.597333-5.034667v-5.034667a42.496 42.496 0 0 1 12.416-27.648l271.530666-271.530666a42.666667 42.666667 0 0 1 60.330667 60.330666z" />
-                        <TextBlock x:Name="LabPage" Text="1" 
-                                   Margin="8,0,13,0" FontSize="15" VerticalAlignment="Center" Foreground="{DynamicResource ColorBrush3}" />
-                        <local:MyIconButton x:Name="BtnPageRight" Margin="0,0,30,0"
-                            Logo="M404.309333 278.357333l241.322667 241.365334-241.322667 241.365333a42.666667 42.666667 0 1 0 60.330667 60.330667l271.530667-271.530667a42.453333 42.453333 0 0 0 11.818666-22.613333l0.597334-5.034667v-5.034667a42.496 42.496 0 0 0-12.416-27.648L464.64 218.026667a42.666667 42.666667 0 0 0-60.330667 60.330666z" />
-                    </StackPanel>
-                </local:MyCard>
-            </StackPanel>
-            <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
-                <local:MyLoading Text="正在获取 Mod 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

+ 0 - 27
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml

@@ -1,27 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-    mc:Ignorable="d" x:Class="PageDownloadNeoForge"
-    PanScroll="{Binding ElementName=PanBack}">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="NeoForge 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Margin="0,0,0,11" Text="NeoForge 是 Minecraft 1.20.1+ 的 Mod 加载器,你需要先安装它才能安装各种 NeoForge 模组,它也兼容一些 Forge 模组。&#xa;本页面提供 NeoForge 安装器下载,在下载后你需要手动打开安装器进行安装。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight"
-                                        EventType="打开网页" EventData="https://neoforged.net/" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
-            <local:MyCard HorizontalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
-                <local:MyLoading Text="正在获取 NeoForge 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

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

@@ -1,34 +0,0 @@
-Public Class PageDownloadNeoForge
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlNeoForgeListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '归类
-            Dim Dict = DlNeoForgeListLoader.Output.Value.GroupBy(Function(d) d.Inherit).OrderByDescending(Function(g) g.Key).
-                ToDictionary(Function(g) g.Key, Function(g) g.ToList())
-            '清空当前
-            PanMain.Children.Clear()
-            '转化为 UI
-            For Each Pair As KeyValuePair(Of String, List(Of DlNeoForgeListEntry)) In Dict
-                If Not Pair.Value.Any() Then Continue For
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 13}
-                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
-                PanMain.Children.Add(NewCard)
-            Next
-        Catch ex As Exception
-            Log(ex, "可视化 NeoForge 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-End Class

+ 0 - 26
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml

@@ -1,26 +0,0 @@
-<local:MyPageRight
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-    mc:Ignorable="d" x:Class="PageDownloadOptiFine"
-    PanScroll="{Binding ElementName=PanBack}">
-    <local:MyScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" x:Name="PanBack">
-        <StackPanel Orientation="Vertical" Margin="5">
-            <local:MyCard Margin="20,20,20,0" Grid.IsSharedSizeScope="True" Title="OptiFine 简介" x:Name="CardTip">
-                <StackPanel Margin="25,40,15,20">
-                    <TextBlock Name="LabConnect" Margin="0,0,0,11" Text="OptiFine 又称为高清修复,以允许安装光影、使用高清材质、提高游戏性能,但与 Mod 的兼容性不佳。" TextWrapping="Wrap" />
-                    <Grid Height="35">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                        </Grid.ColumnDefinitions>
-                        <local:MyButton x:Name="BtnWeb" Grid.Column="0" MinWidth="140" Text="打开官网" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                    </Grid>
-                </StackPanel>
-            </local:MyCard>
-            <StackPanel Name="PanMain" Margin="20,15,20,5" Grid.IsSharedSizeScope="True" />
-            <local:MyCard HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="True" x:Name="PanLoad" UseAnimation="False" Margin="40,50">
-                <local:MyLoading Text="正在获取 OptiFine 列表" Margin="20,20,20,17" x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" />
-            </local:MyCard>
-        </StackPanel>
-    </local:MyScrollViewer>
-</local:MyPageRight>

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

@@ -1,53 +0,0 @@
-Public Class PageDownloadOptiFine
-
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlOptiFineListLoader, AddressOf Load_OnFinish)
-    End Sub
-    Private Sub Init() Handles Me.Loaded
-        PanBack.ScrollToHome()
-    End Sub
-
-    Private Sub Load_OnFinish()
-        '结果数据化
-        Try
-            '归类
-            Dim Dict As New Dictionary(Of String, List(Of DlOptiFineListEntry))
-            Dict.Add("快照版本", New List(Of DlOptiFineListEntry))
-            For VersionCode As Integer = 50 To 0 Step -1
-                Dict.Add("1." & VersionCode, New List(Of DlOptiFineListEntry))
-            Next
-            For Each Version As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value
-                If Version.Inherit.StartsWith("1.") Then
-                    Dim MainVersion As String = "1." & Version.NameDisplay.Split(".")(1).Split(" ")(0)
-                    If Dict.ContainsKey(MainVersion) Then
-                        Dict(MainVersion).Add(Version)
-                    Else
-                        Dict("快照版本").Add(Version)
-                    End If
-                Else
-                    Dict("快照版本").Add(Version)
-                End If
-            Next
-            '清空当前
-            PanMain.Children.Clear()
-            '转化为 UI
-            For Each Pair As KeyValuePair(Of String, List(Of DlOptiFineListEntry)) In Dict
-                If Not Pair.Value.Any() Then Continue For
-                '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 3}
-                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
-                PanMain.Children.Add(NewCard)
-            Next
-        Catch ex As Exception
-            Log(ex, "可视化 OptiFine 版本列表出错", LogLevel.Feedback)
-        End Try
-    End Sub
-
-    Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
-        OpenWebsite("https://www.optifine.net/")
-    End Sub
-
-End Class

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