龙腾猫跃 2 ani în urmă
părinte
comite
83ebc61d8a
37 a modificat fișierele cu 583 adăugiri și 1953 ștergeri
  1. 1 1
      Plain Craft Launcher 2/Application.xaml
  2. 6 5
      Plain Craft Launcher 2/Application.xaml.vb
  3. 1 1
      Plain Craft Launcher 2/Controls/MyResizer.vb
  4. 1 1
      Plain Craft Launcher 2/FormMain.xaml
  5. 13 13
      Plain Craft Launcher 2/FormMain.xaml.vb
  6. BIN
      Plain Craft Launcher 2/Images/Heads/MCBBS.png
  7. 12 6
      Plain Craft Launcher 2/Modules/Base/ModBase.vb
  8. 71 71
      Plain Craft Launcher 2/Modules/Base/ModNet.vb
  9. 7 21
      Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
  10. 2 1
      Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
  11. 15 23
      Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
  12. 3 6
      Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
  13. 8 4
      Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
  14. 14 13
      Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
  15. 39 7
      Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
  16. 1 1
      Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb
  17. 1 1
      Plain Craft Launcher 2/Modules/ModMusic.vb
  18. 2 2
      Plain Craft Launcher 2/My Project/AssemblyInfo.vb
  19. 81 97
      Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
  20. 0 1
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml
  21. 0 4
      Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb
  22. 1 1
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml
  23. 2 2
      Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
  24. 0 559
      Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb
  25. 3 757
      Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
  26. 1 82
      Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb
  27. 2 2
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
  28. 4 0
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
  29. 3 1
      Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb
  30. 33 8
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
  31. 2 4
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
  32. 6 7
      Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
  33. 0 3
      Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
  34. 5 5
      Plain Craft Launcher 2/Resources/Custom.xaml
  35. BIN
      Plain Craft Launcher 2/Resources/Help.zip
  36. 243 243
      Plain Craft Launcher 2/Resources/ModData.txt
  37. BIN
      Plain Craft Launcher 2/Resources/java-wrapper.jar

+ 1 - 1
Plain Craft Launcher 2/Application.xaml

@@ -277,7 +277,7 @@
                 <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
                 <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
                 <Setter Property="UndoLimit" Value="15" />
-                <Setter Property="MaxLength" Value="10000" />
+                <Setter Property="MaxLength" Value="1000" />
                 <Setter Property="BorderBrush" Value="{DynamicResource ColorBrushBg0}" />
                 <Setter Property="Template">
                     <Setter.Value>

+ 6 - 5
Plain Craft Launcher 2/Application.xaml.vb

@@ -47,9 +47,12 @@ Public Class Application
                         Environment.Exit((My.Computer.Info.AvailablePhysicalMemory - Ram) / 1024) '返回清理的内存量(K)
                     End If
 #If DEBUG Then
-                ElseIf e.Args(0) = "--make" Then
                     '制作更新包
-                    UpdateMake(e.Args(1))
+                ElseIf e.Args(0) = "--edit1" Then
+                    ExeEdit(e.Args(1), True)
+                    Environment.Exit(Result.Cancel)
+                ElseIf e.Args(0) = "--edit2" Then
+                    ExeEdit(e.Args(1), False)
                     Environment.Exit(Result.Cancel)
 #End If
                 End If
@@ -112,13 +115,11 @@ Public Class Application
             Setup.Load("SystemDebugMode")
             Setup.Load("SystemDebugAnim")
             Setup.Load("ToolDownloadThread")
+            Setup.Load("ToolDownloadCert")
             '网络配置初始化
             ServicePointManager.Expect100Continue = True
             ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
             ServicePointManager.DefaultConnectionLimit = 1024
-            'ServicePointManager.ServerCertificateValidationCallback = New Security.RemoteCertificateValidationCallback(Function() As Boolean
-            '                                                                                                               Return True
-            '                                                                                                           End Function)
             '计时
             Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
             ApplicationStartTick = GetTimeTick()

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

@@ -128,7 +128,7 @@ Public Class MyResizer
 
     Private Declare Function GetMonitorInfo Lib "user32" (hMonitor As IntPtr, lpmi As MONITORINFO) As Boolean
 
-    Friend Declare Function MonitorFromWindow Lib "User32" (handle As IntPtr, flags As Integer) As IntPtr
+    Private Declare Function MonitorFromWindow Lib "User32" (handle As IntPtr, flags As Integer) As IntPtr
 
     Private Sub connectMouseHandlers(element As UIElement)
         AddHandler element.MouseLeftButtonDown, AddressOf Me.element_MouseLeftButtonDown

+ 1 - 1
Plain Craft Launcher 2/FormMain.xaml

@@ -134,7 +134,7 @@
                     <Grid x:Name="PanTitleInner" Visibility="Collapsed" Margin="-16,0,0,0" Opacity="0" HorizontalAlignment="Left" Width="{Binding ActualWidth, ElementName=PanTitle, Mode=OneWay}">
                         <local:MyIconButton HorizontalAlignment="Left" Theme="White" x:Name="BtnTitleInner" Margin="12,0,0,0" Height="28" Width="28"
                                              LogoScale="0.87" Logo="M1097.162313 584.707482 250.272424 584.707482 562.833701 896.95123C591.749274 925.859979 591.749274 972.659975 562.833701 1001.568724 533.91812 1030.477473 487.058315 1030.477473 458.142742 1001.568724L21.580946 565.451235C6.757449 550.631234-0.361728 531.131238 0.028357 511.679985L0.028357 511.631236 0.028357 511.582486C-0.361728 492.13124 6.757449 472.582488 21.580946 457.762487L458.142742 21.644997C487.058315-7.215002 533.91812-7.215002 562.833701 21.644997 591.749274 50.553746 591.749274 97.4025 562.833701 126.262499L250.272424 438.55499 1097.162313 438.55499C1137.585605 438.55499 1170.304578 471.266238 1170.304578 511.631236 1170.304578 551.947483 1137.585605 584.707482 1097.162313 584.707482L1097.162313 584.707482Z" />
-                        <TextBlock SnapsToDevicePixels="False" IsHitTestVisible="False" Name="LabTitleInner" VerticalAlignment="Center" Margin="47,1,0,0" HorizontalAlignment="Left" Foreground="{DynamicResource ColorBrush8}" FontSize="15" />
+                        <TextBlock SnapsToDevicePixels="False" IsHitTestVisible="False" Name="LabTitleInner" VerticalAlignment="Center" Margin="47,1,60,0" HorizontalAlignment="Left" Foreground="{DynamicResource ColorBrush8}" FontSize="15" />
                     </Grid>
                 </Grid>
                 <Grid x:Name="PanLeft" Grid.Row="1" HorizontalAlignment="Left" SnapsToDevicePixels="True">

+ 13 - 13
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -129,6 +129,12 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '1:BUG-
+        If LastVersion < 314 Then 'Snapshot 2.6.14
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化 MC 下载,尝试解决各种导致 MC 下载失败的问题"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "由于 MCBBS 关站,移除其相关内容"))
+            FeatureCount += 17
+            BugCount += 16
+        End If
         If LastVersion < 312 Then 'Snapshot 2.6.13
             FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动 Forge 1.18.3+ 的 Bug"))
             FeatureCount += 6
@@ -906,7 +912,7 @@ Public Class FormMain
                     Hint("若要安装 Mod,请先选择一个可以安装 Mod 的版本!")
                 ElseIf Not (PageCurrent = PageType.VersionSetup AndAlso PageCurrentSub = PageSubType.VersionMod) Then
                     '未处于 Mod 管理页面
-                    If MyMsgBox($"是否要将这些文件作为 Mod 安装到 {TargetVersion.Name}?", "Mod 安装确认", "确定", "取消") = 1 Then GoTo Install
+                    If MyMsgBox($"是否要将这{If(FilePathList.Count = 1, "个", "些")}文件作为 Mod 安装到 {TargetVersion.Name}?", "Mod 安装确认", "确定", "取消") = 1 Then GoTo Install
                 Else
                     '处于 Mod 管理页面
 Install:
@@ -994,7 +1000,6 @@ Install:
                 Left -= 10000
                 ShowInTaskbar = False
                 Visibility = Visibility.Hidden
-                'SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) Or WS_EX_TOOLWINDOW)
                 Log("[System] 窗口已隐藏,位置:(" & Left & "," & Top & ")")
             Else
                 '取消隐藏
@@ -1017,16 +1022,9 @@ Install:
                     Topmost = False
                     SetForegroundWindow(Handle)
                     Focus()
-                    'ShowWindow(Handle, 1) 'SW_RESTORE
-                    'SetWindowPos(Handle, 0, 0, 0, 0, 0, 3) 'InsertAfter: TOP, NOMOVE & NOSIZE
-                    'SetFocus(Handle)
                     Log("[System] 窗口已置顶,位置:(" & Left & ", " & Top & "), " & Width & " x " & Height)
                 End Sub)
     End Sub
-    'Private Declare Function SetFocus Lib "user32" (hWnd As IntPtr) As IntPtr
-    'Private Declare Function SetWindowPos Lib "user32" (hWnd As IntPtr, hWndInsertAfter As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, flags As UInteger) As Boolean
-    'Private Declare Function AttachThreadInput Lib "user32" (attachFrom As IntPtr, attachTo As IntPtr, isAttach As Boolean) As Boolean
-    'Private Declare Function SendMessage Lib "user32" (hWnd As IntPtr, msg As UInteger, wParam As Long, lParam As Long) As IntPtr
 
 #End Region
 
@@ -1573,7 +1571,11 @@ Install:
     ''' </summary>
     Public Sub BackToTop() Handles BtnExtraBack.Click
         Dim RealScroll As MyScrollViewer = BtnExtraBack_GetRealChild()
-        RealScroll.PerformVerticalOffsetDelta(-RealScroll.VerticalOffset)
+        If RealScroll IsNot Nothing Then
+            RealScroll.PerformVerticalOffsetDelta(-RealScroll.VerticalOffset)
+        Else
+            Log("[UI] 无法返回顶部,未找到合适的 RealScroll", LogLevel.Hint)
+        End If
     End Sub
     Private Function BtnExtraBack_ShowCheck() As Boolean
         Dim RealScroll As MyScrollViewer = BtnExtraBack_GetRealChild()
@@ -1581,9 +1583,7 @@ Install:
     End Function
     Private Function BtnExtraBack_GetRealChild() As MyScrollViewer
         If PanMainRight.Child Is Nothing OrElse TypeOf PanMainRight.Child IsNot MyPageRight Then Return Nothing
-        Dim Scroll = CType(PanMainRight.Child, MyPageRight).PanScroll
-        If Scroll Is Nothing OrElse Scroll.Visibility <> Visibility.Visible Then Return Nothing
-        Return Scroll
+        Return CType(PanMainRight.Child, MyPageRight).PanScroll
     End Function
 
 #End Region

BIN
Plain Craft Launcher 2/Images/Heads/MCBBS.png


+ 12 - 6
Plain Craft Launcher 2/Modules/Base/ModBase.vb

@@ -11,12 +11,12 @@ Public Module ModBase
 #Region "声明"
 
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.6.13" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.6.13." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.6.14" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.6.14." & VersionBranchCode '标准格式的四段式版本号
 #If BETA Then
     Public Const VersionCode As Integer = 313 'Release
 #Else
-    Public Const VersionCode As Integer = 312 'Snapshot
+    Public Const VersionCode As Integer = 314 'Snapshot
 #End If
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -158,9 +158,13 @@ Public Module ModBase
         ''' </summary>
         Public Const IconButtonUser As String = "M660.338 528.065c63.61-46.825 105.131-121.964 105.131-206.83 0-141.7-115.29-256.987-256.997-256.987-141.706 0-256.998 115.288-256.998 256.987 0 85.901 42.52 161.887 107.456 208.562-152.1 59.92-260.185 207.961-260.185 381.077 0 21.276 17.253 38.53 38.53 38.53 21.278 0 38.53-17.254 38.53-38.53 0-183.426 149.232-332.671 332.667-332.671 1.589 0 3.113-0.207 4.694-0.244 0.8 0.056 1.553 0.244 2.362 0.244 183.434 0 332.664 149.245 332.664 332.671 0 21.276 17.255 38.53 38.533 38.53 21.277 0 38.53-17.254 38.53-38.53 0-174.885-110.354-324.13-264.917-382.809z m-331.803-206.83c0-99.22 80.72-179.927 179.935-179.927s179.937 80.708 179.937 179.927c0 99.203-80.721 179.91-179.937 179.91s-179.935-80.708-179.935-179.91z"
         ''' <summary>
-        ''' 图标按钮,微软,0.9x
+        ''' 图标按钮,盾牌,1x
         ''' </summary>
-        Public Const IconButtonMs As String = "M0,0h5v5h-5ZM6,0h5v5h-5v-5ZM0,6h5v5h-5v-5ZM6,6h5v5h-5v-5Z"
+        Public Const IconButtonShield As String = "M511.488256 95.184408c35.310345 22.516742 95.184408 55.78011 167.34033 84.437781 75.738131 29.681159 148.405797 40.93953 191.392304 45.033483v353.615193c0 73.691154-50.662669 164.781609-136.123938 244.101949C649.65917 901.181409 558.568716 942.12094 512 942.12094c-46.568716 0-137.65917-40.93953-222.096952-119.748126C204.441779 742.54073 153.77911 651.450275 153.77911 577.247376v-353.103448c42.474763-4.093953 116.165917-15.352324 191.904048-45.545227 75.226387-30.192904 133.565217-63.456272 165.805098-83.414293M512 0c-4.093953 0-8.187906 1.535232-11.258371 3.582209l-14.84058 10.234882c-1.023488 0.511744-67.550225 47.592204-170.410794 88.531735-100.813593 39.916042-198.556722 41.963018-199.58021 41.963018l-25.075462 0.511744c-10.746627 0.511744-18.934533 8.187906-18.934533 18.422789v414.000999c0 216.97951 286.064968 446.24088 440.09995 446.24088s440.09995-229.261369 440.09995-445.729136V163.758121c0-10.234883-8.69965-18.422789-18.934533-18.422789l-24.563718-0.511744c-1.023488 0-98.766617-2.046977-199.58021-41.963018-103.372314-40.93953-170.410795-88.01999-170.922538-88.531734L523.258371 3.582209c-3.070465-2.558721-7.164418-3.582209-11.258371-3.582209z M743.308346 410.930535l-260.477761 260.477761c-15.864068 15.864068-41.963018 15.864068-57.827087 0l-144.823588-144.823588c-15.864068-15.864068-15.864068-41.963018 0-57.827087 8.187906-8.187906 18.422789-11.770115 29.169415-11.770115 10.234883 0 20.981509 4.093953 29.169416 11.770115l115.654173 115.654173L685.993003 352.591704c15.864068-15.864068 41.963018-15.864068 57.827087 0 15.352324 16.375812 15.352324 42.474763-0.511744 58.338831z"
+        ''' <summary>
+        ''' 图标按钮,离线,0.85x
+        ''' </summary>
+        Public Const IconButtonOffline As String = "M533.293176 788.841412a60.235294 60.235294 0 1 1 85.202824 85.202823l-42.616471 42.586353c-129.355294 129.385412-339.124706 129.385412-468.510117 0-129.385412-129.385412-129.385412-339.124706 0-468.510117l42.586353-42.616471a60.235294 60.235294 0 1 1 85.202823 85.202824l-42.61647 42.586352a210.823529 210.823529 0 1 0 298.164706 298.164706l42.586352-42.61647z m255.548236-255.548236l42.61647-42.586352a210.823529 210.823529 0 1 0-298.164706-298.164706l-42.586352 42.61647a60.235294 60.235294 0 1 1-85.202824-85.202823l42.616471-42.586353c129.355294-129.385412 339.124706-129.385412 468.510117 0 129.385412 129.385412 129.385412 339.124706 0 468.510117l-42.586353 42.616471a60.235294 60.235294 0 1 1-85.202823-85.202824zM192.542118 192.542118a60.235294 60.235294 0 0 1 85.202823 0l553.712941 553.712941a60.235294 60.235294 0 0 1-85.202823 85.202823L192.542118 277.744941a60.235294 60.235294 0 0 1 0-85.202823z"
         ''' <summary>
         ''' 图标,音符,1x
         ''' </summary>
@@ -1980,9 +1984,10 @@ NextElement:
         Log("[System] 执行外部命令并等待返回结果:" & FileName & " " & Arguments)
         Using Program As New Process() With {.StartInfo = Info}
             Program.Start()
+            Dim Result As String = Program.StandardOutput.ReadToEnd & Program.StandardError.ReadToEnd
             Program.WaitForExit(Timeout)
             If Not Program.HasExited Then Program.Kill()
-            Return Program.StandardOutput.ReadToEnd & Program.StandardError.ReadToEnd
+            Return Result
         End Using
     End Function
 
@@ -2111,6 +2116,7 @@ NextElement:
     ''' </summary>
     Public Sub OpenWebsite(Url As String)
         Try
+            If Not Url.ToLower.StartsWith("http") Then Throw New Exception(Url & " 不是一个有效的网址,它必须以 http 开头!")
             Log("[System] 正在打开网页:" & Url)
             Process.Start(Url)
         Catch ex As Exception

+ 71 - 71
Plain Craft Launcher 2/Modules/Base/ModNet.vb

@@ -192,7 +192,6 @@ RequestFinished:
             If Url.StartsWith("https", StringComparison.OrdinalIgnoreCase) Then Request.ProtocolVersion = HttpVersion.Version11
             Request.Timeout = Timeout
             Request.Accept = Accept
-            Request.KeepAlive = False
             SecretHeadersSign(Url, Request)
             Using res As HttpWebResponse = Request.GetResponse()
                 Using HttpStream As Stream = res.GetResponseStream()
@@ -393,7 +392,6 @@ RequestFinished:
             End If
             Req.ContentType = ContentType
             Req.Timeout = Timeout
-            Req.KeepAlive = False
             SecretHeadersSign(Url, Req)
             If Url.StartsWith("https", StringComparison.OrdinalIgnoreCase) Then Req.ProtocolVersion = HttpVersion.Version11
             If Method = "POST" OrElse Method = "PUT" Then
@@ -965,13 +963,12 @@ Capture:
                     '是否禁用多线程,以及规定碎片大小
                     Dim TargetUrl As String = GetSource().Url
                     If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("gitcode.net") OrElse TargetUrl.Contains("github.com") Then Return False
-                    Dim RealFilePieceLimit = If(TargetUrl.Contains("download.mcbbs.net"), FilePieceLimit * 5, FilePieceLimit)
                     '寻找最大碎片
                     Dim FilePieceMax As NetThread = Threads
                     For Each Thread As NetThread In Threads
                         If Thread.DownloadUndone > FilePieceMax.DownloadUndone Then FilePieceMax = Thread
                     Next
-                    If FilePieceMax Is Nothing OrElse FilePieceMax.DownloadUndone < RealFilePieceLimit Then Return False
+                    If FilePieceMax Is Nothing OrElse FilePieceMax.DownloadUndone < FilePieceLimit Then Return False
                     StartPosition = FilePieceMax.DownloadEnd - FilePieceMax.DownloadUndone * 0.4
                     StartSource = GetSource()
 
@@ -1021,7 +1018,7 @@ StartThread:
             Dim HttpRequest As HttpWebRequest
             Dim ResultStream As Stream = Nothing
             '部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟
-            '延迟过长会导致某些特别慢的链接迟迟不被掐死,即使 MCBBS 源在 10s 下也会出现类似情况
+            '延迟过长会导致某些特别慢的链接迟迟不被掐死
             Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Info.Source.FailCount), 30000)
             Info.State = NetState.Connect
             Try
@@ -1206,7 +1203,9 @@ SourceBreak:
                 'End SyncLock
                 Info.Source.Ex = ex
                 '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传)
-                If ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(403)") OrElse ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse
+                'BMCLAPI 的部分源在高频率请求下会返回 403,所以不应因此禁用下载源
+                If ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse
+                   ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的片大小不一致") OrElse
                     (Info.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 40) AndAlso DownloadDone < 1) OrElse
                     Info.Source.FailCount > NetTaskThreadLimit Then
                     Dim IsThisFail As Boolean = False
@@ -1225,7 +1224,7 @@ Wrong:
                     '本线程引发下载源被禁用
                     If IsThisFail Then
                         Log("[Download] " & LocalName & " " & Uuid & "#:下载源被禁用(" & Info.Source.Id & "):" & Info.Source.Url)
-                        Log(ex, "下载源 " & Info.Source.Id & " 已被禁用", If(ex.Message.Contains("不支持断点续传") OrElse ex.Message.Contains("404") OrElse ex.Message.Contains("416"), LogLevel.Developer, LogLevel.Debug))
+                        Log(ex, "下载源 " & Info.Source.Id & " 已被禁用", If(ex.Message.Contains("不支持断点续传") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug))
                         If IsSourceFailed() Then
                             '没有可用源
                             Log("[Download] 文件 " & LocalName & " 已无可用下载源")
@@ -1475,7 +1474,7 @@ Retry:
             End Get
             Set(value As Integer)
                 _FailCount = value
-                If State = LoadState.Loading AndAlso value >= Math.Min(2000, Math.Max(FileRemain * 5.5, NetTaskThreadLimit * 5.5 + 3)) Then
+                If State = LoadState.Loading AndAlso value >= Math.Min(10000, Math.Max(FileRemain * 5.5, NetTaskThreadLimit * 5.5 + 3)) Then
                     Log("[Download] 由于同加载器中失败次数过多引发强制失败:连续失败了 " & value & " 次", LogLevel.Debug)
                     On Error Resume Next
                     Dim ExList As New List(Of Exception)
@@ -1846,69 +1845,70 @@ Retry:
         Private Sub StartManager()
             If IsManagerStarted Then Exit Sub
             IsManagerStarted = True
-            RunInNewThread(Sub()
-                               Try
-                                   Dim LastLoopTime As Long
-                                   While True
-                                       LastLoopTime = GetTimeTick()
-                                       '若已完成,则清空
-                                       If FileRemain = 0 AndAlso Files.Count > 0 Then
-                                           SyncLock LockFiles
-                                               Files.Clear()
-                                           End SyncLock
-                                       End If
-                                       '开启新线程
-                                       If Speed < NetTaskSpeedLimitLow OrElse FileRemain > NetTaskThreadLimit Then
-                                           '速度小于下限或剩余文件还贼多,尝试开启线程
-                                           Dim IsSuccess As Boolean = False
-                                           Dim NewThreadCount As Integer
-                                           '确定最大线程追加数
-                                           NewThreadCount = Math.Max(FileRemain, MathClamp(NetTaskThreadCount / 2, 1, 4))
-                                           NewThreadCount = Math.Floor(NewThreadCount / 2) '双线程启用减半
-                                           NewThreadCount = MathClamp(NewThreadCount, 1, NetTaskThreadLimit)
-                                           '循环追加
-                                           Do
-                                               IsSuccess = False
-                                               '启动 Wait 的文件后立即会变成 Connect,导致第二次循环再次调用,所以需要先暂时存储进去……
-                                               '此外为了减少 LockFiles 的占用时间,所以先遍历列表再开始
-                                               Dim FilesWaiting As New List(Of NetFile)
-                                               Dim FilesLoading As New List(Of NetFile)
-                                               SyncLock LockFiles
-                                                   For Each File As NetFile In Files.Values
-                                                       If File.RandomCode Mod 2 = 0 Then Continue For
-                                                       If File.State = NetState.WaitForDownload Then
-                                                           FilesWaiting.Add(File)
-                                                       ElseIf File.State < NetState.Merge Then
-                                                           FilesLoading.Add(File)
-                                                       End If
-                                                   Next
-                                               End SyncLock
-                                               '为文件列表中的文件开始线程
-                                               For Each File As NetFile In FilesWaiting
-                                                   If NewThreadCount = 0 Then Exit For
-                                                   If File.TryBeginThread() Then
-                                                       IsSuccess = True
-                                                       NewThreadCount -= 1
-                                                   End If
-                                               Next
-                                               For Each File As NetFile In FilesLoading
-                                                   If NewThreadCount = 0 Then Exit For
-                                                   If File.TryBeginThread() Then
-                                                       IsSuccess = True
-                                                       NewThreadCount -= 1
-                                                   End If
-                                               Next
-                                           Loop While NewThreadCount > 0 AndAlso IsSuccess
-                                       End If
-                                       '等待直至 120 ms
-                                       Do While GetTimeTick() - LastLoopTime < 120
-                                           Thread.Sleep(10)
-                                       Loop
-                                   End While
-                               Catch ex As Exception
-                                   Log(ex, "下载管理启动线程 1 出错", LogLevel.Assert)
-                               End Try
-                           End Sub, "NetManager ThreadStarter Single")
+            RunInNewThread(
+            Sub()
+                Try
+                    Dim LastLoopTime As Long
+                    While True
+                        LastLoopTime = GetTimeTick()
+                        '若已完成,则清空
+                        If FileRemain = 0 AndAlso Files.Count > 0 Then
+                            SyncLock LockFiles
+                                Files.Clear()
+                            End SyncLock
+                        End If
+                        '开启新线程
+                        If Speed < NetTaskSpeedLimitLow OrElse FileRemain > NetTaskThreadLimit Then
+                            '速度小于下限或剩余文件还贼多,尝试开启线程
+                            Dim IsSuccess As Boolean = False
+                            Dim NewThreadCount As Integer
+                            '确定最大线程追加数
+                            NewThreadCount = Math.Max(FileRemain, MathClamp(NetTaskThreadCount / 2, 1, 4))
+                            NewThreadCount = Math.Floor(NewThreadCount / 2) '双线程启用减半
+                            NewThreadCount = MathClamp(NewThreadCount, 1, NetTaskThreadLimit)
+                            '循环追加
+                            Do
+                                IsSuccess = False
+                                '启动 Wait 的文件后立即会变成 Connect,导致第二次循环再次调用,所以需要先暂时存储进去……
+                                '此外为了减少 LockFiles 的占用时间,所以先遍历列表再开始
+                                Dim FilesWaiting As New List(Of NetFile)
+                                Dim FilesLoading As New List(Of NetFile)
+                                SyncLock LockFiles
+                                    For Each File As NetFile In Files.Values
+                                        If File.RandomCode Mod 2 = 0 Then Continue For
+                                        If File.State = NetState.WaitForDownload Then
+                                            FilesWaiting.Add(File)
+                                        ElseIf File.State < NetState.Merge Then
+                                            FilesLoading.Add(File)
+                                        End If
+                                    Next
+                                End SyncLock
+                                '为文件列表中的文件开始线程
+                                For Each File As NetFile In FilesWaiting
+                                    If NewThreadCount = 0 Then Exit For
+                                    If File.TryBeginThread() Then
+                                        IsSuccess = True
+                                        NewThreadCount -= 1
+                                    End If
+                                Next
+                                For Each File As NetFile In FilesLoading
+                                    If NewThreadCount = 0 Then Exit For
+                                    If File.TryBeginThread() Then
+                                        IsSuccess = True
+                                        NewThreadCount -= 1
+                                    End If
+                                Next
+                                '等待 40 ms:BMCLAPI 部分时候有 20QPS 的限制
+                                Thread.Sleep(40)
+                            Loop While NewThreadCount > 0 AndAlso IsSuccess
+                        End If
+                        '等待 40 ms
+                        Thread.Sleep(40)
+                    End While
+                Catch ex As Exception
+                    Log(ex, "下载管理启动线程 1 出错", LogLevel.Assert)
+                End Try
+            End Sub, "NetManager ThreadStarter Single")
             RunInNewThread(Sub()
                                Try
                                    Dim LastLoopTime As Long

+ 7 - 21
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -59,7 +59,6 @@
                     Entry.WikiId = i
                     If SplitedLine.Count >= 2 Then
                         Entry.ChineseName = SplitedLine(1)
-                        Entry.MCBBS = If(SplitedLine.Count >= 3, SplitedLine(2), Nothing)
                         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))) & ")")
@@ -82,10 +81,6 @@
         ''' </summary>
         Public ChineseName As String = ""
         ''' <summary>
-        ''' MCBBS 发布帖编号(例如 195107)。若为 Nothing 则没有发布帖。
-        ''' </summary>
-        Public MCBBS As Integer?
-        ''' <summary>
         ''' CurseForge Slug(例如 advanced-solar-panels)。
         ''' </summary>
         Public CurseForgeSlug As String = Nothing
@@ -95,7 +90,7 @@
         Public ModrinthSlug As String = Nothing
 
         Public Overrides Function ToString() As String
-            Return If(CurseForgeSlug, "") & "&" & If(ModrinthSlug, "") & "|" & WikiId & "|" & ChineseName & If(MCBBS Is Nothing, "", "|" & MCBBS)
+            Return If(CurseForgeSlug, "") & "&" & If(ModrinthSlug, "") & "|" & WikiId & "|" & ChineseName
         End Function
     End Class
 
@@ -200,14 +195,6 @@
             End Get
         End Property
         ''' <summary>
-        ''' MCBBS 的帖子 ID。若为 0 则没有。
-        ''' </summary>
-        Public ReadOnly Property MCBBS As Integer
-            Get
-                Return If(DatabaseEntry Is Nothing, 0, If(DatabaseEntry.MCBBS, 0))
-            End Get
-        End Property
-        ''' <summary>
         ''' 翻译后的中文名。若数据库没有则等同于 RawName。
         ''' </summary>
         Public ReadOnly Property TranslatedName As String
@@ -884,10 +871,10 @@ NoSubtitle:
             Dim SearchResult As String = ""
             For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
                 If Not SearchResults(i).AbsoluteRight AndAlso i >= Math.Min(2, SearchResults.Count - 1) Then Exit For '把 3 个结果拼合以提高准确度
-                If SearchResults(i).Item.CurseForgeSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.CurseForgeSlug.Replace("-", " ") & " "
-                If SearchResults(i).Item.ModrinthSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.ModrinthSlug.Replace("-", " ") & " "
+                If SearchResults(i).Item.CurseForgeSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.CurseForgeSlug.Replace("-", " ").Replace("/", " ") & " "
+                If SearchResults(i).Item.ModrinthSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.ModrinthSlug.Replace("-", " ").Replace("/", " ") & " "
                 SearchResult += SearchResults(i).Item.ChineseName.Split(" (").Last.TrimEnd(") ").Split(" - ").First.
-                    Replace(":", "").Replace("(", "").Replace(")", "").ToLower & " "
+                    Replace(":", "").Replace("(", "").Replace(")", "").ToLower.Replace("/", " ") & " "
             Next
             Log("[Comp] 中文搜索原始关键词:" & SearchResult, LogLevel.Developer)
             '去除常见连接词
@@ -911,9 +898,8 @@ NoSubtitle:
         For Each Keyword In AllPossibleKeywords.Split(" ")
             Keyword = Keyword.Trim("["c, "]"c)
             If Keyword = "" Then Continue For
-            If {"forge", "fabric", "for", "mod",
-                "forge/fabric", "fabric/forge", "fabric/quilt", "quilt/fabric"}.Contains(Keyword) Then '#208
-                Log("[Comp] 已跳过搜索关键词:" & Keyword)
+            If {"forge", "fabric", "for", "mod", "quilt"}.Contains(Keyword) Then '#208
+                Log("[Comp] 已跳过搜索关键词:" & Keyword, LogLevel.Developer)
                 Continue For
             End If
             RightKeywords.Add(Keyword)
@@ -927,7 +913,7 @@ NoSubtitle:
         '例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
         If RawFilter.Replace(" ", "").ToLower.Contains("optiforge") Then Task.Input.SearchText = "optiforge"
         If RawFilter.Replace(" ", "").ToLower.Contains("optifabric") Then Task.Input.SearchText = "optifabric"
-        Log("[Comp] 工程列表搜索最终文本:" & Task.Input.SearchText, LogLevel.Developer)
+        Log("[Comp] 工程列表搜索最终文本:" & Task.Input.SearchText, LogLevel.Debug)
         Task.Progress = 0.1
 
 #End Region

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

@@ -583,7 +583,8 @@ Done:
         If LogMc IsNot Nothing Then
             '常规信息
             If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf))
-            If LogMc.Contains("A potential solution has been determined:") OrElse LogMc.Contains("确定了一种可能的解决方法,这样做可能会解决你的问题:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), 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)((\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))
         End If
 
     End Sub

+ 15 - 23
Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb

@@ -221,9 +221,7 @@
     ''' </summary>
     Public DlClientListBmclapiLoader As New LoaderTask(Of Integer, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
     Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of Integer, DlClientListResult))
-        Dim Json As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json",
-                                                       BackupUrl:="https://download.mcbbs.net/mc/game/version_manifest.json",
-                                                       IsJson:=True)
+        Dim Json As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", IsJson:=True)
         Try
             Dim Versions As JArray = Json("versions")
             If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
@@ -384,8 +382,7 @@
     ''' </summary>
     Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain)
     Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
-        Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/optifine/versionList",
-                                                      BackupUrl:="https://download.mcbbs.net/optifine/versionList", IsJson:=True)
+        Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/optifine/versionList", IsJson:=True)
         Try
             Dim Versions As New List(Of DlOptiFineListEntry)
             For Each Token As JObject In Json
@@ -469,8 +466,7 @@
     ''' </summary>
     Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain)
     Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
-        Dim Result As String = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding.Default,
-                                                       BackupUrl:="https://download.mcbbs.net/forge/minecraft")
+        Dim Result As String = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding.Default)
         If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
         '获取所有版本信息
         Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?")
@@ -664,8 +660,7 @@
     ''' Forge 版本列表,BMCLAPI。
     ''' </summary>
     Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
-        Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" & Loader.Input,
-                                                       BackupUrl:="https://download.mcbbs.net/forge/minecraft/" & Loader.Input, IsJson:=True)
+        Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" & Loader.Input, IsJson:=True)
         Dim Versions As New List(Of DlForgeVersionEntry)
         Try
             Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input)
@@ -827,8 +822,7 @@
     ''' </summary>
     Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain)
     Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
-        Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json",
-                                                       BackupUrl:="https://download.mcbbs.net/maven/com/mumfrey/liteloader/versions.json", IsJson:=True)
+        Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", IsJson:=True)
         Try
             Dim Json As JObject = Result("versions")
             Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -914,8 +908,7 @@
     ''' </summary>
     Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain)
     Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
-        Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions",
-                                                       BackupUrl:="https://download.mcbbs.net/fabric-meta/v2/versions", IsJson:=True)
+        Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", IsJson:=True)
         Try
             Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result}
             If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -943,26 +936,25 @@
 
     Public Function DlSourceResourceGet(MojangBase As String) As String()
         MojangBase = MojangBase.Replace("http://resources.download.minecraft.net", "https://resources.download.minecraft.net")
-        Return {MojangBase.Replace("https://piston-data.mojang.com", "https://download.mcbbs.net/assets").Replace("https://piston-meta.mojang.com", "https://download.mcbbs.net/assets").Replace("https://resources.download.minecraft.net", "https://download.mcbbs.net/assets"),
-                MojangBase,
-                MojangBase.Replace("https://piston-data.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")
+        Return {
+                MojangBase.Replace("https://piston-data.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"),
+                MojangBase
                }
     End Function
 
     Public Function DlSourceLibraryGet(MojangBase As String) As String()
-        Return {MojangBase.Replace("https://piston-data.mojang.com", "https://download.mcbbs.net/maven").Replace("https://piston-meta.mojang.com", "https://download.mcbbs.net/maven").Replace("https://libraries.minecraft.net", "https://download.mcbbs.net/maven"),
-                MojangBase.Replace("https://piston-data.mojang.com", "https://download.mcbbs.net/libraries").Replace("https://piston-meta.mojang.com", "https://download.mcbbs.net/libraries").Replace("https://libraries.minecraft.net", "https://download.mcbbs.net/libraries"),
-                MojangBase,
+        Return {
                 MojangBase.Replace("https://piston-data.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"),
-                MojangBase.Replace("https://piston-data.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")
+                MojangBase.Replace("https://piston-data.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"),
+                MojangBase
                }
     End Function
 
     Public Function DlSourceLauncherOrMetaGet(MojangBase As String) As String()
         If MojangBase Is Nothing Then Throw New Exception("无对应的 json 下载地址")
-        Return {MojangBase.Replace("https://piston-data.mojang.com", "https://download.mcbbs.net").Replace("https://piston-meta.mojang.com", "https://download.mcbbs.net").Replace("https://launcher.mojang.com", "https://download.mcbbs.net").Replace("https://launchermeta.mojang.com", "https://download.mcbbs.net"),
-                MojangBase,
-                MojangBase.Replace("https://piston-data.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://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
+        Return {
+                MojangBase.Replace("https://piston-data.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://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com"),
+                MojangBase
                }
     End Function
 

+ 3 - 6
Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb

@@ -727,8 +727,7 @@ Wait:
     Private Sub JavaFileList(Loader As LoaderTask(Of Integer, List(Of NetFile)))
         Log("[Java] 开始获取 Java 下载信息")
         Dim IndexFileStr As String = NetGetCodeByDownload(
-            {"https://download.mcbbs.net/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json",
-             "https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json",
+            {"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)
         '获取下载地址
@@ -740,8 +739,7 @@ Wait:
         Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}")
         '获取文件列表
         Dim ListFileStr As String = NetGetCodeByDownload(
-            {Address.Replace("piston-meta.mojang.com", "download.mcbbs.net"),
-             Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com"),
+            {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com"),
              Address},
         IsJson:=True)
         Dim BaseDir As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Key & "\"
@@ -753,8 +751,7 @@ Wait:
             If Checker.Check(BaseDir & File.Name) Is Nothing Then Continue For '跳过已存在的文件
             Dim Url As String = Info("url")
             Results.Add(New NetFile(
-                            {Url.Replace("piston-data.mojang.com", "download.mcbbs.net"),
-                             Url.Replace("piston-data.mojang.com", "bmclapi2.bangbang93.com"),
+                            {Url.Replace("piston-data.mojang.com", "bmclapi2.bangbang93.com"),
                              Url},
                         BaseDir & File.Name, Checker))
         Next

+ 8 - 4
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -1911,9 +1911,9 @@ IgnoreCustomSkin:
 
         '获取自定义命令
         Dim CustomCommandGlobal As String = Setup.Get("LaunchAdvanceRun")
-        If CustomCommandGlobal <> "" Then CustomCommandGlobal = ArgumentReplace(CustomCommandGlobal)
+        If CustomCommandGlobal <> "" Then CustomCommandGlobal = ArgumentReplace(CustomCommandGlobal, True)
         Dim CustomCommandVersion As String = Setup.Get("VersionAdvanceRun", Version:=McVersionCurrent)
-        If CustomCommandVersion <> "" Then CustomCommandVersion = ArgumentReplace(CustomCommandVersion)
+        If CustomCommandVersion <> "" Then CustomCommandVersion = ArgumentReplace(CustomCommandVersion, True)
 
         '输出 bat
         Try
@@ -2072,7 +2072,7 @@ IgnoreCustomSkin:
         '获取窗口标题
         Dim WindowTitle As String = Setup.Get("VersionArgumentTitle", Version:=McVersionCurrent)
         If WindowTitle = "" Then WindowTitle = Setup.Get("LaunchArgumentTitle")
-        WindowTitle = ArgumentReplace(WindowTitle)
+        WindowTitle = ArgumentReplace(WindowTitle, False)
 
         '初始化等待
         Dim Watcher As New Watcher(Loader, McVersionCurrent, WindowTitle)
@@ -2124,7 +2124,7 @@ IgnoreCustomSkin:
     ''' <summary>
     ''' 在启动结束时,对 PCL 约定的替换标记进行处理。
     ''' </summary>
-    Private Function ArgumentReplace(Raw As String) As String
+    Private Function ArgumentReplace(Raw As String, ReplaceTimeAndDate As Boolean) As String
         If Raw Is Nothing Then Return Nothing
         '路径替换
         Raw = Raw.Replace("{minecraft}", PathMcFolder)
@@ -2134,6 +2134,10 @@ IgnoreCustomSkin:
         '普通替换
         Raw = Raw.Replace("{user}", McLoginLoader.Output.Name)
         Raw = Raw.Replace("{uuid}", McLoginLoader.Output.Uuid)
+        If ReplaceTimeAndDate Then '设置窗口标题时需要动态替换日期和时间
+            Raw = Raw.Replace("{date}", Date.Now.ToString("yyyy/M/d"))
+            Raw = Raw.Replace("{time}", Date.Now.ToString("HH:mm:ss"))
+        End If
         Select Case McLoginLoader.Input.Type
             Case McLoginType.Legacy
                 If PageLinkHiper.HiperState = LoadState.Finished Then

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

@@ -42,15 +42,19 @@
 #Region "读取默认(Original)文件夹,即当前、官启文件夹,可能没有结果"
 
             '扫描当前文件夹
-            If CheckPermission(Path) AndAlso Directory.Exists(Path & "versions\") Then CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Path = Path, .Type = McFolderType.Original})
-            For Each Folder As DirectoryInfo In New DirectoryInfo(Path).GetDirectories
-                If CheckPermission(Folder.FullName) AndAlso Directory.Exists(Folder.FullName & "versions\") OrElse Folder.Name = ".minecraft" Then CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Path = Folder.FullName & "\", .Type = McFolderType.Original})
-            Next
+            Try
+                If Directory.Exists(Path & "versions\") Then CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Path = Path, .Type = McFolderType.Original})
+                For Each Folder As DirectoryInfo In New DirectoryInfo(Path).GetDirectories
+                    If Directory.Exists(Folder.FullName & "versions\") OrElse Folder.Name = ".minecraft" Then CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Path = Folder.FullName & "\", .Type = McFolderType.Original})
+                Next
+            Catch ex As Exception
+                Log(ex, "扫描 PCL 所在文件夹中是否有 MC 文件夹失败")
+            End Try
 
             '扫描官启文件夹
             Dim MojangPath As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\"
-            If (CacheMcFolderList.Count = 0 OrElse MojangPath <> CacheMcFolderList(0).Path) AndAlso '当前文件夹不是官启文件夹
-                CheckPermission(MojangPath) AndAlso Directory.Exists(MojangPath & "versions\") Then '具有权限且存在 versions 文件夹
+            If (Not CacheMcFolderList.Any OrElse MojangPath <> CacheMcFolderList(0).Path) AndAlso '当前文件夹不是官启文件夹
+                Directory.Exists(MojangPath & "versions\") Then '具有权限且存在 versions 文件夹
                 CacheMcFolderList.Add(New McFolder With {.Name = "官方启动器文件夹", .Path = MojangPath, .Type = McFolderType.Original})
             End If
 
@@ -406,7 +410,7 @@
                         End If
                         '无法获取
                         _Version.McName = "Unknown"
-                        Info = "PCL 无法识别该版本,请向作者反馈此问题"
+                        Info = "PCL 无法识别该版本的 MC 版本号"
                     Catch ex As Exception
                         Log(ex, "识别 Minecraft 版本时出错")
                         _Version.McName = "Unknown"
@@ -1915,7 +1919,7 @@ NextVersion:
                 '获取下载信息
                 Try
                     Log("[Minecraft] 开始获取 Authlib-Injector 下载信息")
-                    DownloadInfo = GetJson(NetGetCodeByDownload({"https://download.mcbbs.net/mirrors/authlib-injector/artifact/latest.json", "https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"}, IsJson:=True))
+                    DownloadInfo = GetJson(NetGetCodeByDownload({"https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"}, IsJson:=True))
                 Catch ex As Exception
                     Log(ex, "获取 Authlib-Injector 下载信息失败")
                 End Try
@@ -1926,9 +1930,7 @@ NextVersion:
                         '开始下载
                         Dim DownloadAddress As String = DownloadInfo("download_url")
                         Log("[Minecraft] Authlib-Injector 需要更新:" & DownloadAddress, LogLevel.Developer)
-                        Result.Add(New NetFile({
-                                DownloadAddress.Replace("bmclapi2.bangbang93.com", "download.mcbbs.net"), DownloadAddress
-                            }, TargetFile, New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString)))
+                        Result.Add(New NetFile({DownloadAddress}, TargetFile, New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString)))
                     End If
                 End If
             End If
@@ -1958,7 +1960,7 @@ NextVersion:
                 '获取 Url 的真实地址
                 Urls.Add(Token.Url)
                 If Token.Url.Contains("launcher.mojang.com/v1/objects") Then Urls = DlSourceLauncherOrMetaGet(Token.Url).ToList() 'Mappings
-                If Token.Url.Contains("maven") Then Urls.Insert(0, Token.Url.Replace(Mid(Token.Url, 1, Token.Url.IndexOf("maven")), "https://download.mcbbs.net/").Replace("maven.fabricmc.net", "maven").Replace("maven.minecraftforge.net", "maven"))
+                If Token.Url.Contains("maven") Then Urls.Insert(0, Token.Url.Replace(Mid(Token.Url, 1, Token.Url.IndexOf("maven")), "https://bmclapi2.bangbang93.com/").Replace("maven.fabricmc.net", "maven").Replace("maven.minecraftforge.net", "maven"))
             End If
             If Token.LocalPath.Contains("transformer-discovery-service") Then
                 'Transformer 文件释放
@@ -1970,7 +1972,6 @@ NextVersion:
                 Dim OptiFineBase As String = Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries\optifine\OptiFine\", "").Split("_")(0) & "/" & GetFileNameFromPath(Token.LocalPath).Replace("-", "_")
                 OptiFineBase = "/maven/com/optifine/" & OptiFineBase
                 If OptiFineBase.Contains("_pre") Then OptiFineBase = OptiFineBase.Replace("com/optifine/", "com/optifine/preview_")
-                Urls.Add("https://download.mcbbs.net" & OptiFineBase)
                 Urls.Add("https://bmclapi2.bangbang93.com" & OptiFineBase)
             ElseIf Urls.Count <= 2 Then
                 '普通文件

+ 39 - 7
Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb

@@ -13,6 +13,14 @@ Public Module ModMod
         Public Sub New(Path As String)
             Me.Path = If(Path, "")
         End Sub
+        ''' <summary>
+        ''' Mod 的完整路径,去除最后的 .disabled 和 .old。
+        ''' </summary>
+        Public ReadOnly Property RawPath As String
+            Get
+                Return GetPathFromFullPath(Path) & RawFileName
+            End Get
+        End Property
 
         ''' <summary>
         ''' Mod 的完整文件名。
@@ -768,6 +776,18 @@ VersionFindFail:
         End Property
         Private _ModrinthHash As String
 
+#End Region
+
+#Region "API"
+
+        Public Overrides Function ToString() As String
+            Return $"{State} - {Path}"
+        End Function
+        Public Overrides Function Equals(obj As Object) As Boolean
+            Dim target = TryCast(obj, McMod)
+            Return target IsNot Nothing AndAlso Path = target.Path
+        End Function
+
 #End Region
 
         ''' <summary>
@@ -844,6 +864,17 @@ VersionFindFail:
                 '加载 McMod 对象
                 Dim ModEntry As New McMod(ModFile.FullName)
                 ModEntry.Load()
+                Dim DumpMod As McMod = ModList.FirstOrDefault(Function(m) m.RawFileName = ModEntry.RawFileName)
+                If DumpMod IsNot Nothing Then
+                    Dim DisabledMod As McMod = If(DumpMod.State = McMod.McModState.Disabled, DumpMod, ModEntry)
+                    Log($"[Mod] 重复的 Mod 文件:{DumpMod.FileName} 与 {ModEntry.FileName},已忽略 {DisabledMod.FileName}", LogLevel.Debug)
+                    If DisabledMod Is ModEntry Then
+                        Continue For
+                    Else
+                        ModList.Remove(DisabledMod)
+                        ModUpdateList.Remove(DisabledMod)
+                    End If
+                End If
                 ModList.Add(ModEntry)
                 '读取 Comp 缓存
                 If ModEntry.State = McMod.McModState.Unavaliable Then Continue For
@@ -860,13 +891,14 @@ VersionFindFail:
             Log($"[Mod] 共有 {ModList.Count} 个 Mod,其中 {ModUpdateList.Where(Function(m) m.Comp Is Nothing).Count} 个需要联网获取信息,{ModUpdateList.Where(Function(m) m.Comp IsNot Nothing).Count} 个需要更新信息")
 
             '排序
-            ModList = Sort(ModList, Function(Left As McMod, Right As McMod) As Boolean
-                                        If (Left.State = McMod.McModState.Unavaliable) <> (Right.State = McMod.McModState.Unavaliable) Then
-                                            Return Left.State = McMod.McModState.Unavaliable
-                                        Else
-                                            Return Not Right.FileName.CompareTo(Left.FileName)
-                                        End If
-                                    End Function)
+            ModList = Sort(ModList,
+            Function(Left As McMod, Right As McMod) As Boolean
+                If (Left.State = McMod.McModState.Unavaliable) <> (Right.State = McMod.McModState.Unavaliable) Then
+                    Return Left.State = McMod.McModState.Unavaliable
+                Else
+                    Return Not Right.FileName.CompareTo(Left.FileName)
+                End If
+            End Function)
 
             '回设
             If Loader.IsAborted Then Exit Sub

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

@@ -96,7 +96,7 @@
                                        '设置窗口标题
                                        For i = 1 To 3
                                            If IsWindowFinished AndAlso IsWindowAppeared AndAlso WindowTitle <> "" AndAlso State = MinecraftState.Running AndAlso Not GameProcess.HasExited Then
-                                               Dim RealTitle As String = WindowTitle.Replace("{time}", Date.Now.ToShortTimeString)
+                                               Dim RealTitle As String = WindowTitle.Replace("{date}", Date.Now.ToString("yyyy/M/d")).Replace("{time}", Date.Now.ToString("HH:mm:ss"))
                                                SetWindowText(WindowHandle, RealTitle.ToCharArray)
                                            End If
                                            Thread.Sleep(64)

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

@@ -25,7 +25,7 @@
                 For Each File In EnumerateFiles(Path & "PCL\Musics\")
                     '文件夹可能会被加入 .ini 文件夹配置文件、一些乱七八糟的 .jpg 文件啥的
                     Dim Ext As String = File.Extension.ToLower
-                    If {"ini", "jpg", "txt", "cfg", "lrc", "db", "png"}.Contains(Ext) Then Continue For
+                    If {".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png"}.Contains(Ext) Then Continue For
                     MusicAllList.Add(File.FullName)
                 Next
             End If

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

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

+ 81 - 97
Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb

@@ -519,10 +519,8 @@ Public Module ModDownloadLib
                 Dim Sources As New List(Of String)
                 'BMCLAPI 源
                 If DownloadInfo.IsPreview Then
-                    Sources.Add("https://download.mcbbs.net/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
                     Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
                 Else
-                    Sources.Add("https://download.mcbbs.net/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
                     Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
                 End If
                 '官方源
@@ -682,30 +680,28 @@ Retry:
         Dim Loaders As New List(Of LoaderBase)
         '获取下载地址
         Loaders.Add(New LoaderTask(Of DlOptiFineListEntry, List(Of NetFile))("获取 OptiFine 下载地址",
-                                                            Sub(Task As LoaderTask(Of DlOptiFineListEntry, List(Of NetFile)))
-                                                                Dim Sources As New List(Of String)
-                                                                'BMCLAPI 源
-                                                                If DownloadInfo.IsPreview Then
-                                                                    Sources.Add("https://download.mcbbs.net/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
-                                                                    Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
-                                                                Else
-                                                                    Sources.Add("https://download.mcbbs.net/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
-                                                                    Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
-                                                                End If
-                                                                '官方源
-                                                                Dim PageData As String
-                                                                Try
-                                                                    PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html")
-                                                                    Task.Progress = 0.8
-                                                                    Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
-                                                                    Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources(0))
-                                                                Catch ex As Exception
-                                                                    Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败")
-                                                                End Try
-                                                                Task.Progress = 0.9
-                                                                '构造文件请求
-                                                                Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, TargetFolder, New FileChecker(MinSize:=64 * 1024))}
-                                                            End Sub) With {.ProgressWeight = 6})
+        Sub(Task As LoaderTask(Of DlOptiFineListEntry, List(Of NetFile)))
+            Dim Sources As New List(Of String)
+            'BMCLAPI 源
+            If DownloadInfo.IsPreview Then
+                Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
+            Else
+                Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
+            End If
+            '官方源
+            Dim PageData As String
+            Try
+                PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html")
+                Task.Progress = 0.8
+                Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
+                Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources(0))
+            Catch ex As Exception
+                Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败")
+            End Try
+            Task.Progress = 0.9
+            '构造文件请求
+            Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, TargetFolder, New FileChecker(MinSize:=64 * 1024))}
+        End Sub) With {.ProgressWeight = 6})
         '下载
         Loaders.Add(New LoaderDownload("下载 OptiFine 主文件", New List(Of NetFile)) With {.ProgressWeight = 10, .Block = True})
         Return Loaders
@@ -852,22 +848,11 @@ Retry:
                     Case "1.6.2"
                         Address.Add("https://dl.liteloader.com/redist/1.6.2/liteloader-installer-1.6.2-04.jar")
                     Case "1.5.2"
-                        'Address.AddRange({"https://download.mcbbs.net/maven/com/mumfrey/liteloader/1.5.2/liteloader-1.5.2.jar",
-                        '                  "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/1.5.2/liteloader-1.5.2.jar"})
                         Address.Add("https://dl.liteloader.com/redist/1.5.2/liteloader-installer-1.5.2-01.jar")
                     Case Else
                         Throw New NotSupportedException("未知的 Minecraft 版本(" & DownloadInfo.Inherit & ")")
                 End Select
             Else
-                'BMCLAPI 源下载的是安装后的 Jar,而不是 Installer,故无法使用
-                ''BMCLAPI 源
-                'If DownloadInfo.IsPreview Then
-                '    Address.AddRange({"https://download.mcbbs.net/maven/com/mumfrey/liteloader/" & DownloadInfo.Inherit & "-SNAPSHOT/liteloader-" & DownloadInfo.Inherit & "-SNAPSHOT.jar",
-                '                      "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/" & DownloadInfo.Inherit & "-SNAPSHOT/liteloader-" & DownloadInfo.Inherit & "-SNAPSHOT.jar"})
-                'Else
-                '    Address.AddRange({"https://download.mcbbs.net/maven/com/mumfrey/liteloader/" & DownloadInfo.Inherit & "/liteloader-" & DownloadInfo.Inherit & ".jar",
-                '                      "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/" & DownloadInfo.Inherit & "/liteloader-" & DownloadInfo.Inherit & ".jar"})
-                'End If
                 '官方源
                 Address.Add("http://jenkins.liteloader.com/job/LiteLoaderInstaller%20" & DownloadInfo.Inherit & "/lastSuccessfulBuild/artifact/" & If(DownloadInfo.Inherit = "1.8", "ant/dist/", "build/libs/") & DownloadInfo.FileName)
             End If
@@ -1097,15 +1082,14 @@ Retry:
             Dim Loaders As New List(Of LoaderBase)
             '获取下载地址
             Loaders.Add(New LoaderTask(Of DlForgeVersionEntry, List(Of NetFile))("获取下载地址",
-                                                            Sub(Task As LoaderTask(Of DlForgeVersionEntry, List(Of NetFile)))
-                                                                Dim Files As New List(Of NetFile)
-                                                                Files.Add(New NetFile({
-                                                                        "https://download.mcbbs.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
-                                                                        "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
-                                                                        "https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName
-                                                                    }, Target, New FileChecker(MinSize:=64 * 1024, Hash:=DownloadInfo.Hash)))
-                                                                Task.Output = Files
-                                                            End Sub) With {.ProgressWeight = 0.1, .Show = False})
+            Sub(Task As LoaderTask(Of DlForgeVersionEntry, List(Of NetFile)))
+                Dim Files As New List(Of NetFile)
+                Files.Add(New NetFile({
+                    "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
+                    "https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & DownloadInfo.Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName
+                }, Target, New FileChecker(MinSize:=64 * 1024, Hash:=DownloadInfo.Hash)))
+                Task.Output = Files
+            End Sub) With {.ProgressWeight = 0.1, .Show = False})
             '下载
             Loaders.Add(New LoaderDownload("下载主文件", New List(Of NetFile)) With {.ProgressWeight = 6})
 
@@ -1123,7 +1107,7 @@ Retry:
 
     Private Sub ForgeInjector(Target As String, Task As LoaderTask(Of Boolean, Boolean), McFolder As String, UseJavaWrapper As Boolean)
         '选择 Java
-        Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
+        Dim Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 60))
         If Java Is Nothing Then
             If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。")
             '开始自动下载
@@ -1133,7 +1117,7 @@ Retry:
                 Thread.Sleep(10)
             Loop
             '检查下载结果
-            Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0))
+            Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 60))
             If Task.IsAborted Then Exit Sub
             If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。")
         End If
@@ -1160,50 +1144,52 @@ Retry:
             Dim LastResults As New Queue(Of String)
             Using outputWaitHandle As New AutoResetEvent(False)
                 Using errorWaitHandle As New AutoResetEvent(False)
-                    AddHandler process.OutputDataReceived, Function(sender, e)
-                                                               Try
-                                                                   If e.Data Is Nothing Then
-                                                                       outputWaitHandle.[Set]()
-                                                                   Else
-                                                                       LastResults.Enqueue(e.Data)
-                                                                       If LastResults.Count > 100 Then LastResults.Dequeue()
-                                                                       ForgeInjectorLine(e.Data, Task)
-                                                                   End If
-                                                               Catch ex As ObjectDisposedException
-                                                               Catch ex As Exception
-                                                                   Log(ex, "读取 Forge 安装器信息失败")
-                                                               End Try
-                                                               Try
-                                                                   If Task.State = LoadState.Aborted AndAlso Not process.HasExited Then
-                                                                       Log("[Installer] 由于任务取消,已中止 Forge 安装")
-                                                                       process.Kill()
-                                                                   End If
-                                                               Catch
-                                                               End Try
-                                                               Return Nothing
-                                                           End Function
-                    AddHandler process.ErrorDataReceived, Function(sender, e)
-                                                              Try
-                                                                  If e.Data Is Nothing Then
-                                                                      errorWaitHandle.[Set]()
-                                                                  Else
-                                                                      LastResults.Enqueue(e.Data)
-                                                                      If LastResults.Count > 100 Then LastResults.Dequeue()
-                                                                      ForgeInjectorLine(e.Data, Task)
-                                                                  End If
-                                                              Catch ex As ObjectDisposedException
-                                                              Catch ex As Exception
-                                                                  Log(ex, "读取 Forge 安装器错误信息失败")
-                                                              End Try
-                                                              Try
-                                                                  If Task.State = LoadState.Aborted AndAlso Not process.HasExited Then
-                                                                      Log("[Installer] 由于任务取消,已中止 Forge 安装")
-                                                                      process.Kill()
-                                                                  End If
-                                                              Catch
-                                                              End Try
-                                                              Return Nothing
-                                                          End Function
+                    AddHandler process.OutputDataReceived,
+                    Function(sender, e)
+                        Try
+                            If e.Data Is Nothing Then
+                                outputWaitHandle.[Set]()
+                            Else
+                                LastResults.Enqueue(e.Data)
+                                If LastResults.Count > 100 Then LastResults.Dequeue()
+                                ForgeInjectorLine(e.Data, Task)
+                            End If
+                        Catch ex As ObjectDisposedException
+                        Catch ex As Exception
+                            Log(ex, "读取 Forge 安装器信息失败")
+                        End Try
+                        Try
+                            If Task.State = LoadState.Aborted AndAlso Not process.HasExited Then
+                                Log("[Installer] 由于任务取消,已中止 Forge 安装")
+                                process.Kill()
+                            End If
+                        Catch
+                        End Try
+                        Return Nothing
+                    End Function
+                    AddHandler process.ErrorDataReceived,
+                    Function(sender, e)
+                        Try
+                            If e.Data Is Nothing Then
+                                errorWaitHandle.[Set]()
+                            Else
+                                LastResults.Enqueue(e.Data)
+                                If LastResults.Count > 100 Then LastResults.Dequeue()
+                                ForgeInjectorLine(e.Data, Task)
+                            End If
+                        Catch ex As ObjectDisposedException
+                        Catch ex As Exception
+                            Log(ex, "读取 Forge 安装器错误信息失败")
+                        End Try
+                        Try
+                            If Task.State = LoadState.Aborted AndAlso Not process.HasExited Then
+                                Log("[Installer] 由于任务取消,已中止 Forge 安装")
+                                process.Kill()
+                            End If
+                        Catch
+                        End Try
+                        Return Nothing
+                    End Function
                     process.Start()
                     process.BeginOutputReadLine()
                     process.BeginErrorReadLine()
@@ -1321,8 +1307,7 @@ Retry:
                 '添加主文件
                 Dim Files As New List(Of NetFile) From {New NetFile({
                     "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
-                    "https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName,
-                    "https://download.mcbbs.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName
+                    "https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Inherit & "-" & DownloadInfo.FileVersion & "/" & DownloadInfo.FileName
                 }, InstallerAddress, New FileChecker(MinSize:=64 * 1024, Hash:=DownloadInfo.Hash))}
                 Task.Output = Files
             End Sub) With {.ProgressWeight = 0.5, .Show = False})
@@ -1650,7 +1635,7 @@ Retry:
         RunInNewThread(Sub()
                            Try
                                Log("[Download] 刷新 Forge 推荐版本缓存开始")
-                               Dim Result As String = NetGetCodeByDownload({"https://bmclapi2.bangbang93.com/forge/promos", "https://download.mcbbs.net/forge/promos"})
+                               Dim Result As String = NetGetCodeByDownload("https://bmclapi2.bangbang93.com/forge/promos")
                                If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")")
                                Dim ResultJson As JContainer = GetJson(Result)
                                '获取所有推荐版本列表
@@ -1721,7 +1706,6 @@ Retry:
             '下载
             'BMCLAPI 不支持 Fabric Installer 下载
             Dim Address As New List(Of String)
-            'Address.Add(Url.Replace("maven.fabricmc.net", "download.mcbbs.net/maven"))
             Address.Add(Url)
             Loaders.Add(New LoaderDownload("下载主文件", New List(Of NetFile) From {New NetFile(Address.ToArray, Target, New FileChecker(MinSize:=1024 * 64))}) With {.ProgressWeight = 15})
             '启动
@@ -1759,7 +1743,7 @@ Retry:
                                                                 Task.Progress = 0.5
                                                                 '构造文件请求
                                                                 Task.Output = New List(Of NetFile) From {New NetFile({
-                                                                    "https://download.mcbbs.net/fabric-meta/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json",
+                                                                    "https://bmclapi2.bangbang93.com/fabric-meta/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json",
                                                                     "https://meta.fabricmc.net/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json"
                                                                 }, VersionFolder & Id & ".json", New FileChecker(IsJson:=True))}
                                                                 '新建 mods 文件夹

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

@@ -16,7 +16,6 @@
                     <StackPanel Orientation="Horizontal" Height="35">
                         <local:MyButton x:Name="BtnIntroWeb"   MinWidth="140" Text="转到 CurseForge" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
                         <local:MyButton x:Name="BtnIntroWiki"  MinWidth="140" Text="转到 MC 百科" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
-                        <local:MyButton x:Name="BtnIntroMCBBS" MinWidth="140" Text="转到 MCBBS" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                         <local:MyButton x:Name="BtnIntroCopy"  MinWidth="140" Text="复制名称" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                     </StackPanel>
                 </StackPanel>

+ 0 - 4
Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb

@@ -145,7 +145,6 @@
         '决定按钮显示
         BtnIntroWeb.Text = If(Project.FromCurseForge, "转到 CurseForge", "转到 Modrinth")
         BtnIntroWiki.Visibility = If(Project.WikiId = 0, Visibility.Collapsed, Visibility.Visible)
-        BtnIntroMCBBS.Visibility = If(Project.MCBBS = 0, Visibility.Collapsed, Visibility.Visible)
 
         AniControlEnabled -= 1
     End Sub
@@ -323,9 +322,6 @@
     Private Sub BtnIntroWiki_Click(sender As Object, e As EventArgs) Handles BtnIntroWiki.Click
         OpenWebsite("https://www.mcmod.cn/class/" & Project.WikiId & ".html")
     End Sub
-    Private Sub BtnIntroMCBBS_Click(sender As Object, e As EventArgs) Handles BtnIntroMCBBS.Click
-        OpenWebsite("https://www.mcbbs.net/thread-" & Project.MCBBS & "-1-1.html")
-    End Sub
     Private Sub BtnIntroCopy_Click(sender As Object, e As EventArgs) Handles BtnIntroCopy.Click
         ClipboardSet(CompItem.LabTitle.Text)
     End Sub

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

@@ -33,7 +33,7 @@
                     <Path Name="PathTypeOne" HorizontalAlignment="Left" RenderTransformOrigin="0.5,0.5" Margin="12,0,0,0" Stretch="Uniform" Fill="{DynamicResource ColorBrush3}" MaxHeight="16" MaxWidth="16" VerticalAlignment="Center"
                           Data="">
                         <Path.RenderTransform>
-                            <ScaleTransform ScaleX="1.2" ScaleY="1.2" />
+                            <ScaleTransform ScaleX="1.05" ScaleY="1.05" />
                         </Path.RenderTransform>
                     </Path>
                     <TextBlock Name="LabTypeOne" Text="" FontSize="14" VerticalAlignment="Center" Grid.Column="1" Foreground="{DynamicResource ColorBrush3}" Margin="15,0,12,0" Grid.ColumnSpan="2" SnapsToDevicePixels="False" UseLayoutRounding="False" />

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

@@ -286,7 +286,7 @@ UnknownType:
                 Setup.Set("LoginType", McLoginType.Ms)
                 PanType.Visibility = Visibility.Collapsed
                 PanTypeOne.Visibility = Visibility.Visible
-                PathTypeOne.Data = (New GeometryConverter).ConvertFromString(Logo.IconButtonMs)
+                PathTypeOne.Data = (New GeometryConverter).ConvertFromString(Logo.IconButtonShield)
                 LabTypeOne.Text = "正版登录"
                 RadioLoginType5.Visibility = Visibility.Visible
                 RadioLoginType0.Visibility = Visibility.Collapsed
@@ -295,7 +295,7 @@ UnknownType:
                 Setup.Set("LoginType", McLoginType.Legacy)
                 PanType.Visibility = Visibility.Collapsed
                 PanTypeOne.Visibility = Visibility.Visible
-                PathTypeOne.Data = (New GeometryConverter).ConvertFromString(Logo.IconButtonUser)
+                PathTypeOne.Data = (New GeometryConverter).ConvertFromString(Logo.IconButtonOffline)
                 LabTypeOne.Text = "离线登录"
             Case 3 '统一通行证
                 If Setup.Get("CacheNideAccess") = "" Then

+ 0 - 559
Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb

@@ -58,307 +58,21 @@
 
     '检查网络状态
     Private Shared Sub InitPingCheck(Task As LoaderTask(Of Integer, Integer))
-        PingTime = 0
-        Try
-            Log("[HiPer] 网络检测:连通检测开始")
-            Dim StartTime = Date.Now.Ticks
-            Dim Result As String = NetGetCodeByClient("https://www.baidu.com/duty/", Encoding.UTF8, 20000, "")
-            Dim DuringTime As Integer = (Date.Now.Ticks - StartTime) / 10000
-            If Result.Contains("百度") Then
-                Log("[HiPer] 网络检测:连通检测成功(" & DuringTime & "ms)")
-                PingTime = DuringTime
-            Else
-                Log("[HiPer] 网络检测:连通检测失败(获取的内容有误)")
-                PingTime = -1
-            End If
-        Catch ex As ThreadInterruptedException
-        Catch ex As Exception
-            Log(ex, "连通检测失败")
-            PingTime = -1
-        End Try
     End Sub
     Private Shared Sub InitIpCheck()
-        IpCheckStatus = LoadState.Loading
-        Try
-            Log("[HiPer] 网络检测:IP 地址检测开始")
-            Dim IpCheckResult As String = NetRequestOnce("https://ipinfo.io/json", "GET", "", "application/json", 10000)
-            Dim Country As String = GetJson(IpCheckResult)("country")
-            IpIsInChina = Country = "CN"
-            IpCheckStatus = LoadState.Finished
-            Log("[HiPer] 网络检测:IP 地址检测结果:" & Country)
-        Catch ex As ThreadInterruptedException
-            IpCheckStatus = LoadState.Aborted
-        Catch ex As Exception
-            Log(ex, "IP 地址检测失败")
-            IpCheckStatus = LoadState.Failed
-            IpIsInChina = True
-        End Try
     End Sub
     Private Shared PingTime As Integer, IpCheckStatus As LoadState = LoadState.Loading, IpIsInChina As Boolean
     Private Shared Sub InitCheck(Task As LoaderTask(Of Integer, Integer))
-        '检查协议
-        If Not Setup.Get("LinkEula") Then
-Reopen:
-            Select Case MyMsgBox("PCL 的联机服务由速聚授权提供。" & vbCrLf & "在使用前,你需要同意速聚的用户服务协议和隐私政策。", "协议授权", "同意", "拒绝", "查看用户服务协议和隐私政策")
-                Case 1
-                    Setup.Set("LinkEula", True)
-                Case 2
-                    Throw New Exception("$你拒绝了用户服务协议……")
-                Case 3
-                    OpenWebsite("https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzkxMTMyODk3Mg==&action=getalbum&album_id=2585385685407514625&scene=173&from_msgid=2247483720&from_itemidx=1&count=3&nolastread=1#wechat_redirect")
-                    GoTo Reopen
-            End Select
-        End If
-        '等待网络环境检查加载器结束
-        SetLoadDesc("正在检查网络环境……", "检查网络环境")
-        Do While Task.State = LoadState.Loading AndAlso Not (PingTime <> 0 AndAlso IpCheckStatus <> LoadState.Loading)
-            Thread.Sleep(50)
-        Loop
-        '获取网络环境检查结果
-        If IpCheckStatus = LoadState.Finished AndAlso Not IpIsInChina Then
-            If MyMsgBox("检测到你的 IP 不在中国,这会导致联机变得非常不稳定。" & vbCrLf &
-                        "如果你正开着加速器或者 VPN,请先关闭它们,然后再继续……", "警告", "继续", "取消", IsWarn:=True, ForceWait:=True) = 2 Then
-                Throw New Exception("$请在关闭加速器或者 VPN 后点击重试。")
-            End If
-            Task.Progress = 0.5
-            InitIpCheck() '重新进行检查
-        End If
-        '没有联网
-        If IpCheckStatus = LoadState.Failed AndAlso PingTime = -1 Then
-            Throw New Exception("$PCL 没法连上网……" & vbCrLf & "如果你改变了网络环境,请重启 PCL。")
-        End If
     End Sub
 
     '获取所需文件
     Private Shared Sub InitGetFile(Task As LoaderTask(Of Integer, List(Of NetFile)))
-        '初始化
-        SetLoadDesc("正在初始化……", "初始化")
-        Directory.CreateDirectory(PathHiper)
-        WriteFile(PathHiper & "HiPer 联机模块.vbs", "createobject(""wscript.shell"").run """"""" & PathHiper & "HiPer 联机模块.exe"""" v"", 0", Encoding:=Encoding.GetEncoding("GB18030")) '准备启动脚本
-        '获取索引码文件
-        SetLoadDesc("正在获取索引码文件……", "获取索引码文件")
-        Dim Cert As String = RunInUiWait(Function() FrmLinkHiper.TextCert.Text)
-        Log("[Hiper] 联机索引码:" & Cert)
-        If Cert = Setup.Get("LinkHiperCertLast") AndAlso File.Exists(PathHiper & "cert.yml") Then
-            Log("[Hiper] 索引码与上次输入的一致,跳过获取步骤")
-        Else
-            Dim CertRaw As String
-            Try
-                CertRaw = NetRequestOnce("https://cert.mcer.cn/" & Cert & ".yml", "GET", "", "")
-            Catch ex As Exception
-                If GetExceptionSummary(ex).Contains("(404)") Then
-                    Throw New CertOutdatedException '索引码无效或已过期
-                ElseIf GetExceptionSummary(ex).Contains("too many requests") Then
-                    Throw New Exception("你的尝试太频繁了,请暂时啥都别点,等两分钟后再试……")
-                Else
-                    Throw
-                End If
-            End Try
-            If Not CertRaw.Contains("WARNING <<< AUTO SYNC AREA") Then Throw New Exception("获取到的索引码文件内容有误!")
-            WriteFile(PathHiper & "cert.yml", CertRaw)
-            Setup.Set("LinkHiperCertLast", Cert)
-            Setup.Set("LinkHiperCertTime", "")
-        End If
-        Task.Progress = 0.25
-        '获取 CPU 架构
-        SetLoadDesc("正在获取 CPU 架构……", "获取 CPU 架构")
-        Dim Architecture As String
-        Select Case GetType(String).Assembly.GetName().ProcessorArchitecture
-            Case Reflection.ProcessorArchitecture.X86
-                Architecture = "386"
-            Case Reflection.ProcessorArchitecture.Amd64
-                Architecture = "amd64"
-            Case Reflection.ProcessorArchitecture.Arm
-                Architecture = "arm64"
-            Case Else
-                Architecture = "arm64"
-                Log("[Hiper] 当前 CPU 架构为 " & GetStringFromEnum(GetType(String).Assembly.GetName().ProcessorArchitecture) & ",没有最适合的项,这可能会导致联机模块无法启动!", LogLevel.Debug)
-        End Select
-        Log("[Hiper] CPU 架构:" & Architecture)
-        '检查更新:hiper.exe
-        SetLoadDesc("正在检查联机模块本体更新……", "检查联机模块本体更新")
-        Dim RequiredFiles As New List(Of NetFile)
-        Dim Checksums As String = Nothing, ChecksumHiper As String = Nothing
-        If File.Exists(PathHiper & "HiPer 联机模块.exe") Then
-            Try
-                Checksums = NetGetCodeByDownload({
-                    "http://mirror.hiper.cn.s2.the.bb/packages.sha1",
-                    "http://mirror.hiper.cn.s3.the.bb:175/packages.sha1",
-                    "https://gitcode.net/to/hiper/-/raw/master/packages.sha1",
-                    "https://cert.mcer.cn/mirror/packages.sha1"
-                })
-            Catch ex As Exception
-                Log(ex, "获取联机模块更新信息失败,将会强制启动联机模块", LogLevel.Hint)
-                GoTo FinishHiperFileCheck
-            End Try
-            ChecksumHiper = RegexSeek(Checksums, "[0-9a-f]{40}(?=  windows-" & Architecture & "/hiper.exe)")
-            If ChecksumHiper Is Nothing Then
-                Log("[Hiper] 未找到联机模块更新信息,将会强制启动联机模块", LogLevel.Hint)
-                GoTo FinishHiperFileCheck
-            End If
-            Log("[Hiper] hiper.exe 的所需 SHA1:" & ChecksumHiper)
-            If ChecksumHiper = GetFileSHA1(PathHiper & "HiPer 联机模块.exe") Then
-                Log("[Hiper] hiper.exe 文件校验通过,无需重新下载")
-                GoTo FinishHiperFileCheck
-            End If
-        End If
-        Log("[Hiper] 需要重新下载 hiper.exe")
-        RequiredFiles.Add(New NetFile({"http://mirror.hiper.cn.s2.the.bb/windows-" & Architecture & "/hiper.exe",
-                                       "http://mirror.hiper.cn.s3.the.bb:175/windows-" & Architecture & "/hiper.exe",
-                                       "https://gitcode.net/to/hiper/-/raw/master/windows-" & Architecture & "/hiper.exe?inline=false",
-                                       "https://cert.mcer.cn/mirror/windows-" & Architecture & "/hiper.exe"},
-                                      PathHiper & "HiPer 联机模块.exe",
-                                      New FileChecker(Hash:=ChecksumHiper, MinSize:=1024 * 200))) 'Hash 的默认值即为 Nothing,所以 Checksum 可以为 Nothing
-FinishHiperFileCheck:
-        Task.Progress = 0.5
-        '检查更新:wintun.dll
-        Dim ChecksumWintun As String = Nothing
-        If File.Exists(PathHiper & "wintun.dll") Then
-            Try
-                If Checksums Is Nothing Then
-                    Checksums = NetGetCodeByDownload({
-                        "http://mirror.hiper.cn.s2.the.bb/packages.sha1",
-                        "http://mirror.hiper.cn.s3.the.bb:175/packages.sha1",
-                        "https://gitcode.net/to/hiper/-/raw/master/packages.sha1",
-                        "https://cert.mcer.cn/mirror/packages.sha1"
-                    })
-                End If
-            Catch ex As Exception
-                Log(ex, "获取联机模块更新信息失败,将会强制启动联机模块", LogLevel.Hint)
-                GoTo FinishWintunFileCheck
-            End Try
-            ChecksumWintun = RegexSeek(Checksums, "[0-9a-f]{40}(?=  windows-" & Architecture & "/wintun.dll)")
-            If ChecksumWintun Is Nothing Then
-                Log("[Hiper] 未找到联机模块更新信息,将会强制启动联机模块", LogLevel.Hint)
-                GoTo FinishWintunFileCheck
-            End If
-            Log("[Hiper] wintun.dll 的所需 SHA1:" & ChecksumWintun)
-            If ChecksumWintun = GetFileSHA1(PathHiper & "wintun.dll") Then
-                Log("[Hiper] wintun.dll 文件校验通过,无需重新下载")
-                GoTo FinishWintunFileCheck
-            End If
-        End If
-        Log("[Hiper] 需要重新下载 wintun.dll")
-        RequiredFiles.Add(New NetFile({"http://mirror.hiper.cn.s2.the.bb/windows-" & Architecture & "/wintun.dll",
-                                       "http://mirror.hiper.cn.s3.the.bb:175/windows-" & Architecture & "/wintun.dll",
-                                       "https://gitcode.net/to/hiper/-/raw/master/windows-" & Architecture & "/wintun.dll?inline=false",
-                                       "https://cert.mcer.cn/mirror/windows-" & Architecture & "/wintun.dll"},
-                                      PathHiper & "wintun.dll",
-                                      New FileChecker(Hash:=ChecksumWintun, MinSize:=1024 * 200))) 'Hash 的默认值即为 Nothing,所以 Checksum 可以为 Nothing
-FinishWintunFileCheck:
-        Task.Progress = 0.75
-        '检查更新:MCB
-        If Not IsServerSide Then
-            SetLoadDesc("正在检查联机模块组件更新……", "检查联机模块组件更新")
-            Dim ChecksumMcb As String = Nothing
-            If File.Exists(PathHiper & "MCB 联机模块.exe") Then
-                Try
-                    Checksums = NetGetCodeByDownload({
-                        "http://mirror.hiper.cn.s2.the.bb/utils/minecraft-broadcast/packages.sha1",
-                        "http://mirror.hiper.cn.s3.the.bb:175/utils/minecraft-broadcast/packages.sha1",
-                        "https://gitcode.net/to/hiper/-/raw/master/utils/minecraft-broadcast/packages.sha1",
-                        "https://cert.mcer.cn/mirror/utils/minecraft-broadcast/packages.sha1"
-                    })
-                Catch ex As Exception
-                    Log(ex, "获取联机模块组件更新信息失败,将会强制启动联机模块组件", LogLevel.Hint)
-                    GoTo FinishMcbFileCheck
-                End Try
-                ChecksumMcb = RegexSeek(Checksums, "[0-9a-f]{40}(?=  mcb-windows-" & Architecture & ".exe)")
-                If ChecksumMcb Is Nothing Then
-                    Log("[Hiper] 未找到联机模块组件更新信息,将会强制启动联机模块组件", LogLevel.Hint)
-                    GoTo FinishMcbFileCheck
-                End If
-                Log("[Hiper] mcb.exe 的所需 SHA1:" & ChecksumMcb)
-                If ChecksumMcb = GetFileSHA1(PathHiper & "MCB 联机模块.exe") Then
-                    Log("[Hiper] mcb.exe 文件校验通过,无需重新下载")
-                    GoTo FinishMcbFileCheck
-                End If
-            End If
-            Log("[Hiper] 需要重新下载 mcb.exe")
-            RequiredFiles.Add(New NetFile({
-                                       "http://mirror.hiper.cn.s2.the.bb/utils/minecraft-broadcast/mcb-windows-" & Architecture & ".exe",
-                                       "http://mirror.hiper.cn.s3.the.bb:175/utils/minecraft-broadcast/mcb-windows-" & Architecture & ".exe",
-                                       "https://gitcode.net/to/hiper/-/raw/master/utils/minecraft-broadcast/mcb-windows-" & Architecture & ".exe?inline=false",
-                                       "https://cert.mcer.cn/mirror/utils/minecraft-broadcast/mcb-windows-" & Architecture & ".exe"},
-                                      PathHiper & "MCB 联机模块.exe",
-                                      New FileChecker(Hash:=ChecksumMcb, MinSize:=1024 * 200))) 'Hash 的默认值即为 Nothing,所以 Checksum 可以为 Nothing
-FinishMcbFileCheck:
-        End If
-        '添加 point.yml
-        RequiredFiles.Add(New NetFile({"https://cert.mcer.cn/point.yml"}, PathHiper & "point.yml", New FileChecker(MinSize:=200)))
-        '开始下载
-        SetLoadDesc("正在下载联机模块……", "下载联机模块")
-        Task.Output = RequiredFiles
     End Sub
     Public Class CertOutdatedException
         Inherits Exception
     End Class
     '启动联机模块
     Private Shared Sub InitLaunch(Task As LoaderTask(Of Integer, Integer))
-        '关闭运行中的 HiPer,然后再刷新凭证文件
-        SetLoadDesc("正在关闭运行中的联机模块……", "关闭运行中的联机模块")
-        HiperStop(True)
-        '准备凭证文件
-        SetLoadDesc("正在准备索引码文件……", "准备索引码文件")
-        Dim CertFile As String = ReadFile(PathHiper & "cert.yml")
-        '添加防火墙配置
-        If IsServerSide Then
-            CertFile += ("\n\ninbound:\n  port: " & HostPort & "\n  proto: tcp\n  host: any").Replace("\n", vbCrLf)
-        Else
-            CertFile += ("\n\noutbound:\n  port: " & HostPort & "\n  proto: tcp\n  host: " & HostIp).Replace("\n", vbCrLf)
-        End If
-        CertFile += "\nlogging:\n  format: json".Replace("\n", vbCrLf)
-        '更新 point.yml 段
-        Const SyncAreaRegex As String = "(?<=#[ ]*WARNING >>> AUTO SYNC AREA)[\s\S]+?(?=#[ ]*WARNING <<< AUTO SYNC AREA)"
-        CertFile = CertFile.Replace(RegexSeek(CertFile, SyncAreaRegex), RegexSeek(ReadFile(PathHiper & "point.yml"), SyncAreaRegex))
-        WriteFile(PathHiper & "config.yml", CertFile)
-        AllNodes = RegexSearch(CertFile.Replace(vbLf, "cr"), "(?<="")[0-9]+.[0-9]+.[0-9]+.[0-9]+(?=""( )?:( )?cr    - "")").Distinct.
-                   Where(Function(l) Not (l = "6.6.1.1" OrElse l = "6.6.2.2" OrElse l = "6.6.3.3")).ToList
-        If AllNodes.Count < 2 Then Throw New Exception("$索引码文件格式有误,未找到节点 IP!")
-        Task.Progress = 0.05
-        '尝试启动
-        HiperStart(Task) '启动失败会直接抛出异常
-        Task.Progress = 0.2
-        '检查连接情况
-        SetLoadDesc("正在连接到联机节点 (1/2)……" & If(IpIsInChina, "", vbCrLf & "由于网络不在中国,可能要花费很长时间。"), "检查联机节点")
-        If Not IsServerSide AndAlso HostIp = HiperIp Then Throw New Exception("$还搁这自己连自己?!不是吧?!")
-        Dim PingNodeResults As New List(Of Integer)
-        Dim UnpingedNodes As New List(Of String)(AllNodes)
-        Do Until PingNodeResults.Count >= 2 OrElse Task.IsAborted
-            Dim SelectedNode = RandomOne(UnpingedNodes)
-            Dim PingNode = Ping(SelectedNode, 4000)
-            If PingNode = -1 Then
-                Task.Progress += (If(PingNodeResults.Count = 0, 0.58, 0.88) - Task.Progress) * 0.3
-            Else
-                SetLoadDesc("正在连接到联机节点 (2/2)……" & If(IpIsInChina, "", vbCrLf & "由于网络不在中国,可能要花费很长时间。"), "检查联机节点")
-                Task.Progress = 0.6
-                PingNodeResults.Add(PingNode)
-                UnpingedNodes.Remove(SelectedNode)
-            End If
-        Loop
-        PingNodes = PingNodeResults.Min
-        Task.Progress = 0.9
-        '与房主 Ping 一下看看
-        If Not IsServerSide Then
-            SetLoadDesc("正在连接到房主……", "检查房主")
-            HostPing = -1 '实际上不使用本次 Ping 的结果,因为它真的偏大……
-            Do
-                Dim PingHost = Ping(HostIp, 4000, False)
-                If PingHost = -1 Then
-                    Thread.Sleep(500)
-                Else
-                    Exit Do
-                End If
-            Loop Until Task.IsAborted
-            '在等待 2 秒后再次触发 Ping,以刷新显示的 Ping
-            RunInNewThread(Sub()
-                               Thread.Sleep(2000)
-                               TaskPingHost.Start(True, IsForceRestart:=True)
-                           End Sub, "HiPer Delayed Ping")
-        End If
-        '刷新完成页面
-        SetLoadDesc("正在加载完成页面……", "加载完成页面")
-        RunInUiWait(AddressOf FrmLinkHiper.WatcherTimer1)
     End Sub
     Private Shared PingNodes As Integer, AllNodes As List(Of String)
 
@@ -389,40 +103,6 @@ FinishMcbFileCheck:
     ''' </summary>
     Public Shared Function HiperStop(SleepWhenKilled As Boolean) As Boolean
         Return False
-        HiperStop = False
-        '修改凭证
-        Dim ConfigContent As String = ReadFile(PathHiper & "config.yml")
-        If Not ConfigContent.Contains("enable: false") Then
-            WriteFile(PathHiper & "config.yml", ConfigContent & vbCrLf & "enable: false")
-        End If
-        '关闭所有进程
-        For Each ProcessObject In Process.GetProcesses
-            Dim IsHiper As Boolean = ProcessObject.ProcessName = "HiPer 联机模块"
-            Dim IsMcb As Boolean = ProcessObject.ProcessName = "MCB 联机模块"
-            If Not IsHiper AndAlso Not IsMcb Then Continue For
-            HiperStop = True
-            Try
-                If IsMcb Then
-                    ProcessObject.Kill()
-                    Log("[HiPer] 已结束进程 PID " & ProcessObject.Id & ":" & ProcessObject.ProcessName)
-                    If HiperStop AndAlso SleepWhenKilled Then Thread.Sleep(1000) '等待 1 秒确认进程已退出
-                Else 'IsHiper
-                    If SleepWhenKilled Then
-                        Thread.Sleep(4000) '等待 4 秒确认进程已退出
-                        If Not ProcessObject.HasExited Then
-                            ProcessObject.Kill()
-                            Log("[HiPer] 已结束进程 PID " & ProcessObject.Id & ":" & ProcessObject.ProcessName)
-                        End If
-                    End If
-                End If
-            Catch ex As Exception
-                Log(ex, "结束进程失败(" & ProcessObject.Id & "," & ProcessObject.ProcessName & ")")
-            End Try
-        Next
-        '初始化
-        HiperState = LoadState.Waiting
-        HiperProcessId = -1 : McbProcessId = -1
-        HiperCertTime = Nothing
     End Function
     ''' <summary>
     ''' 启动程序,并等待初始化完成后退出运行,同时更新 HiperIp。
@@ -430,125 +110,10 @@ FinishMcbFileCheck:
     ''' 若程序正在运行,则会先停止其运行。
     ''' </summary>
     Public Shared Sub HiperStart(Task As LoaderTask(Of Integer, Integer))
-        Try
-            SetLoadDesc("正在启动联机模块……", "启动联机模块")
-            PossibleFailReason = Nothing
-            '启动 Hiper
-            Log("[Hiper] 启动 Hiper 进程")
-            DeleteDirectory(PathHiper & "logs") '清理日志
-            Dim HiperInfo = New ProcessStartInfo With {
-                .FileName = "wscript",
-                .Arguments = """" & PathHiper & "HiPer 联机模块.vbs""",
-                .Verb = "runas" '需要管理员权限
-            }
-            Dim HiperVbsProcess As New Process() With {.StartInfo = HiperInfo}
-            HiperVbsProcess.Start()
-            '查找真正的 Hiper 进程
-            SetLoadDesc("联机模块正在启动……", "联机模块加载")
-            Do
-                Dim GotProcesses = Process.GetProcesses.
-                    Where(Function(l) l.ProcessName = "HiPer 联机模块" AndAlso Math.Abs((l.StartTime - Date.Now).TotalMinutes) < 1)
-                If GotProcesses.Count > 0 Then
-                    HiperProcessId = GotProcesses.First.Id
-                    Log("[Hiper] 已发现 Hiper 进程,PID:" & HiperProcessId)
-                    Exit Do
-                End If
-                Thread.Sleep(50)
-            Loop While Task.State = LoadState.Loading AndAlso Not Task.IsAborted
-            If HiperProcessId = -1 Then Throw New Exception("联机模块未能成功启动!")
-            HiperState = LoadState.Loading
-            Task.Progress = 0.15
-            '抓取日志
-            Dim LogLine As Integer = 0
-            Do
-                Thread.Sleep(100)
-                If Not File.Exists(PathHiper & "logs\hiper.log") Then Continue Do
-                Dim LogLines As String() = ReadFile(PathHiper & "logs\hiper.log").TrimEnd(vbLf).Split(vbLf)
-                For i = LogLine To LogLines.Count - 1
-                    HiperLogLine(LogLines(i), Task)
-                Next
-                LogLine = LogLines.Count
-            Loop While HiperState = LoadState.Loading AndAlso Not Task.IsAborted AndAlso Process.GetProcesses.Any(Function(l) l.Id = HiperProcessId)
-            '输出
-            If HiperState = LoadState.Finished Then
-                Log("[Hiper] Hiper 启动完成")
-            ElseIf PossibleFailReason IsNot Nothing AndAlso PossibleFailReason.Contains("索引码已过期") Then
-                Throw New CertOutdatedException()
-            Else
-                Throw New Exception(If(PossibleFailReason, "联机模块因未知原因启动失败!"))
-            End If
-            '启动 MCB
-            Task.Progress = 0.25
-            If Not IsServerSide Then
-                Try
-                    Log("[Hiper] 启动 MCB 进程")
-                    Dim McbInfo = New ProcessStartInfo With {
-                        .FileName = PathHiper & "MCB 联机模块.exe", .WorkingDirectory = PathHiper,
-                        .UseShellExecute = False, .CreateNoWindow = True,
-                        .RedirectStandardError = True, .RedirectStandardOutput = True,
-                        .Arguments = "-addr " & HostIp & ":" & HostPort & " -motd ""PCL 联机房间"""
-                    }
-                    Dim McbProcess As New Process() With {.StartInfo = McbInfo}
-                    McbProcess.Start()
-                    McbProcessId = McbProcess.Id
-                Catch ex As Exception
-                    Throw New Exception("联机模块组件启动失败", ex)
-                End Try
-            End If
-        Catch ex As Exception
-            Try
-                HiperStop(True) '由于启动失败停止进程
-            Catch
-            End Try
-            HiperState = LoadState.Failed
-            Throw
-        End Try
     End Sub
 
     'Hiper 日志
     Private Shared Sub HiperLogLine(Content As String, Task As LoaderTask(Of Integer, Integer))
-
-        '检查报错
-        Dim ContentTest As String = Content.ToLower
-        Dim ErrorMessage As String = Nothing
-        Dim ContentJson As JObject = Nothing
-        If Content.StartsWith("{""") AndAlso Content.EndsWith("}") Then
-            ContentJson = GetJson(Content)
-            ContentTest = If(ContentJson("error"), ContentJson("msg")).ToString.ToLower
-            ErrorMessage = ContentJson("error")
-        End If
-        If ContentTest.Contains("hiper certificate for this host is expired") Then
-            PossibleFailReason = "$你的索引码已过期,请更换新的索引码!"
-        ElseIf ContentTest.Contains("error creating interface: access is denied") Then
-            PossibleFailReason = "$没有获得管理员权限,无法创建网络通道!"
-        ElseIf ContentTest.Contains("cannot create a file when that file already exists") Then
-            PossibleFailReason = "$请不要重复开启多个联机模块!"
-        ElseIf Content.Contains("failed to load config") Then
-            PossibleFailReason = "$索引码文件内容存在错误!"
-        ElseIf Content.Contains("system cannot find the file specified") Then
-            PossibleFailReason = "$创建网络通道失败!" & vbCrLf & "如果你曾启动过 VPN 软件,请先打开那个软件,然后关掉它,最后再重试。虽然不知道为啥,但这样大概管用……"
-        End If
-        If PossibleFailReason Is Nothing AndAlso ErrorMessage IsNot Nothing Then PossibleFailReason = ErrorMessage
-
-        '检查结束
-        If ContentJson IsNot Nothing Then
-            Dim Message As String = ContentJson("msg")
-            If Message = "HiPer interface is active" Then
-                HiperIp = ContentJson("network").ToString.Split("/").First
-                If IsServerSide Then HostIp = HiperIp
-                Log("[Hiper] Hiper 启动完成,IP:" & HiperIp)
-                HiperState = LoadState.Finished
-            ElseIf Message = "Validity of client certificate" Then
-                Dim VaildTime As String = ContentJson("valid")
-                Date.TryParseExact(VaildTime, "yyyy-MM-dd HH:mm:ss", Globalization.CultureInfo.InvariantCulture, Globalization.DateTimeStyles.None, HiperCertTime)
-                Log("[Hiper] 索引码到期时间:" & HiperCertTime.ToString)
-                Setup.Set("LinkHiperCertTime", HiperCertTime.ToString)
-            End If
-        End If
-
-        '写入日志
-        If ModeDebug OrElse Content.Contains("""error""") Then Log("[Hiper] " & Content)
-
     End Sub
     Private Shared PossibleFailReason As String = Nothing
 
@@ -643,42 +208,6 @@ FinishMcbFileCheck:
     '每 15 秒执行的 Timer
     Private Shared HostPing As Integer = -1
     Private Sub WatcherTimer15()
-        If Not (HiperState = LoadState.Finished OrElse HiperState = LoadState.Loading) Then Exit Sub
-        '检查 HiPer 崩溃
-        Try
-            Process.GetProcessById(HiperProcessId)
-        Catch
-            Dim LogLines = ReadFile(PathHiper & "logs\hiper.log").TrimEnd(vbLf).Split(vbLf)
-            HiperExit(False)
-            MyMsgBox("由于联机模块异常退出,已退出联机房间。" & vbCrLf & vbCrLf &
-                     "联机模块最后的日志:" & vbCrLf & Join(LogLines.Skip(LogLines.Count - 3).ToList, vbCrLf), "联机断开")
-        End Try
-        '下面的部分需要完全完成加载
-        If HiperState <> LoadState.Finished Then Exit Sub
-        '检查 MCB 崩溃
-        Try
-            If Not IsServerSide Then Process.GetProcessById(McbProcessId)
-        Catch
-            HiperExit(False)
-            MyMsgBox("由于 MCB 联机模块异常退出,已退出联机房间。", "联机断开")
-        End Try
-        '重新检查 Ping
-        If Not IsServerSide Then
-            Dim PingHostFailedCount As Integer = 0
-            Do
-                TaskPingHost.WaitForExit(True, IsForceRestart:=True)
-                If TaskPingHost.Output = -1 Then
-                    PingHostFailedCount += 1
-                    Log("[HiPer] Ping 房主失败(第 " & PingHostFailedCount & " 次)")
-                    If PingHostFailedCount >= 3 Then
-                        HiperExit(False) : MyMsgBox("与房主断开连接,已退出联机房间。", "联机断开") : Exit Sub
-                    End If
-                Else
-                    If PingHostFailedCount > 0 Then Log("[HiPer] Ping 房主已恢复成功(" & HostPing & "ms)")
-                    Exit Do
-                End If
-            Loop While True
-        End If
     End Sub
 
 #End Region
@@ -711,14 +240,6 @@ FinishMcbFileCheck:
 
     '创建房间
     Private Sub BtnSelectCreate_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles BtnSelectCreate.MouseLeftButtonUp
-        ''获取端口号
-        'Dim PortInput As String = MyMsgBoxInput("", New ObjectModel.Collection(Of Validate) From {
-        '                                           New ValidateInteger(2, 65535),
-        '                                           New ValidateExceptSame({"55555", "55557"}, "端口不能为 %!")
-        '                                       }, "在 MC 的暂停画面选择【对局域网开放】", "输入端口号", "确定", "取消")
-        'If PortInput Is Nothing Then Exit Sub
-        ''开始
-        'RoomCreate(Val(PortInput))
     End Sub
     Private Sub RoomCreate(Port As Integer)
         '记录信息
@@ -730,34 +251,6 @@ FinishMcbFileCheck:
 
     '加入房间
     Private Sub BtnSelectJoin_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles BtnSelectJoin.MouseLeftButtonUp
-        '        '获取信息
-        '        Dim Code As String = MyMsgBoxInput("", New ObjectModel.Collection(Of Validate) From {New ValidateLength(8, 99)}, "",
-        '                        "输入联机码", "确定", "取消")
-        '        If Code Is Nothing Then Exit Sub
-        '        '记录信息
-        '        If Not Code.EndsWith(RequestVersion) OrElse Not Code.StartsWith("P") Then GoTo WrongCode
-        '        Dim Ip As String, Port As Integer
-        '        Try
-        '            Dim IpAndPort As Long = RadixConvert(Code.Substring(1, Code.Length - 2).ToUpper, 36, 10)
-        '            'IpAndPort = HostPort + IpParts(0) * 65536 + IpParts(1) * 65536 * 256 + IpParts(2) * 65536 * 256 * 256 + IpParts(3) * 65536 * 256 * 256 * 256
-        '            Ip = Math.Floor(IpAndPort / (65536L * 256 * 256 * 256))
-        '            IpAndPort = IpAndPort Mod (65536L * 256 * 256 * 256)
-        '            Ip = Math.Floor(IpAndPort / (65536L * 256 * 256)) & "." & Ip
-        '            IpAndPort = IpAndPort Mod (65536L * 256 * 256)
-        '            Ip = Math.Floor(IpAndPort / (65536L * 256)) & "." & Ip
-        '            IpAndPort = IpAndPort Mod (65536L * 256)
-        '            Ip = Math.Floor(IpAndPort / (65536L)) & "." & Ip
-        '            Port = IpAndPort Mod 65536
-        '        Catch
-        '            GoTo WrongCode
-        '        End Try
-        '        '启动
-        '        RoomJoin(Ip, Port)
-        '        Exit Sub
-        'WrongCode:
-        '        If Not Code.StartsWith("P") AndAlso Code.Length >= 49 Then Hint("你输入的可能是 IOI 的联机码,请在左侧的联机方式中选择 IOI!", HintType.Critical) : Exit Sub
-        '        If Code.StartsWith("P") AndAlso Not Code.EndsWith(RequestVersion) Then Hint("你的 PCL 版本与房主的 PCL 版本不一致!", HintType.Critical) : Exit Sub
-        '        Hint("你输入的联机码无效!", HintType.Critical)
     End Sub
     Private Sub RoomJoin(Ip As String, Port As Integer)
         '记录信息
@@ -773,54 +266,6 @@ FinishMcbFileCheck:
 
     '承接状态切换的 UI 改变
     Private Sub OnLoadStateChanged(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
-        Select Case NewState
-            Case LoadState.Loading
-                UpdateProgress(0)
-                If IsServerSide Then
-                    LabLoadTitle.Text = "正在创建联机房间"
-                    Log("[Hiper] 正在创建联机房间,端口 " & HostPort)
-                Else
-                    LabLoadTitle.Text = "正在加入联机房间"
-                    Log("[Hiper] 正在加入联机房间,目标 IP " & HostIp & ":" & HostPort)
-                End If
-                LabLoadDesc.Text = "正在初始化……"
-                LoadStep = "准备初始化"
-            Case LoadState.Failed
-                UpdateProgress(1)
-                If IsServerSide Then
-                    LabLoadTitle.Text = "创建联机房间失败"
-                Else
-                    LabLoadTitle.Text = "加入联机房间失败"
-                End If
-                Dim RealException As Exception = If(Loader.Error.InnerException, Loader.Error)
-                If TypeOf RealException Is CertOutdatedException Then
-                    LabCertTitle.Text = "索引码无效"
-                    LabCertDesc.Text = "你的 HiPer 索引码无效或者已经过期!" & vbCrLf & "请重新输入索引码。"
-                    HiperExit(True)
-                ElseIf RealException.Message.StartsWith("$") Then
-                    LabLoadDesc.Text = RealException.Message.TrimStart("$") & vbCrLf &
-                                       "点击镐子重试,或者点击灰色的 × 取消。"
-                Else
-                    LabLoadDesc.Text = LoadStep & "失败:" & GetExceptionSummary(RealException) & vbCrLf &
-                                       "点击镐子重试,或者点击灰色的 × 取消。"
-                End If
-                Log(Loader.Error, "HiPer 联机尝试失败")
-            Case LoadState.Finished
-                UpdateProgress(1)
-                CurrentSubpage = Subpages.PanFinish
-                BtnFinishPing.Visibility = If(IsServerSide, Visibility.Collapsed, Visibility.Visible)
-                LineFinishPing.Visibility = If(IsServerSide, Visibility.Collapsed, Visibility.Visible)
-                BtnFinishCopy.Visibility = If(IsServerSide, Visibility.Visible, Visibility.Collapsed)
-                If IsServerSide Then
-                    LabFinishTitle.Text = "已创建联机房间"
-                    LabFinishDesc.Text = "已在端口 " & HostPort & " 创建了联机房间。" & vbCrLf & "点击下方的复制联机码按钮,然后把联机码发给朋友吧!"
-                Else
-                    LabFinishTitle.Text = "已加入联机房间"
-                    LabFinishDesc.Text = "启动游戏,进入多人游戏页面后稍等片刻,房间就会出现在服务器列表的最下方。" & vbCrLf & "如果提示无效会话,不要退出联机房间,重新启动游戏即可!"
-                End If
-                LabFinishIp.Text = HostIp & ":" & HostPort
-                Log("[Hiper] 已完成连接")
-        End Select
     End Sub
     Private Shared LoadStep As String = "准备初始化"
     Private Shared Sub SetLoadDesc(Intro As String, [Step] As String)
@@ -888,10 +333,6 @@ FinishMcbFileCheck:
 
     '复制联机码
     Private Sub BtnFinishCopy_Click(sender As Object, e As EventArgs) Handles BtnFinishCopy.Click
-        Dim IpParts = HostIp.Split(".").Select(Function(l) Val(l)).ToList
-        Dim IpAndPort As Long = HostPort + IpParts(0) * 65536 + IpParts(1) * 65536 * 256 + IpParts(2) * 65536 * 256 * 256 + IpParts(3) * 65536 * 256 * 256 * 256
-        ClipboardSet("P" & RadixConvert(IpAndPort, 10, 36) & RequestVersion, False)
-        Hint("已复制联机码!", HintType.Finish)
     End Sub
 
     'Ping 房主

+ 3 - 757
Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb

@@ -1,204 +1,10 @@
-Imports System.Net
-Imports System.Net.Sockets
+Imports System.Net.Sockets
 
 Public Class PageLinkIoi
     Public Const RequestVersion As Integer = 4
     Public Const IoiVersion As Integer = 10 '由于已关闭更新渠道,在提升 IoiVersion 时必须提升 RequestVersion
     Public Shared PathIoi As String = PathAppdata & "联机模块\IOI 联机模块.exe"
 
-#Region "初始化"
-
-    '页面初始化
-    Private IsLoad As Boolean = False
-    Private Sub MeLoaded() Handles Me.Loaded
-        FormMain.EndProgramForce(Result.Aborted)
-
-        '重复加载部分
-        PanBack.ScrollToHome()
-        RefreshUi()
-
-        '非重复加载部分
-        If IsLoad Then Exit Sub
-        IsLoad = True
-
-        '更新线程
-        RunInNewThread(Sub()
-                           Do While True
-                               Thread.Sleep(200)
-                               If FrmMain.PageCurrent.Page = FormMain.PageType.Link AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.LinkIoi Then RunInUiWait(Sub() FrmLinkIoi.RefreshUi())
-                               RefreshWorker()
-                           Loop
-                       End Sub, "Link Timer")
-
-    End Sub
-
-    '加载器初始化与左边栏处理
-    Private Sub LoaderInit() Handles Me.Initialized
-        PageLoaderInit(Load, PanLoad, PanBack, PanAlways, InitLoader, Sub()
-                                                                      End Sub)
-    End Sub
-
-    '初始化加载器与步骤
-    Public Shared WithEvents InitLoader As New LoaderCombo(Of Integer)("联机模块初始化", {
-        New LoaderTask(Of Integer, Integer)("端口检查", AddressOf InitPortCheck) With {.ProgressWeight = 1},
-        New LoaderTask(Of Integer, Integer)("启动请求核心", AddressOf StartSocketListener) With {.ProgressWeight = 1},
-        New LoaderTask(Of Integer, List(Of NetFile))("初次启动尝试", AddressOf InitFirst) With {.ProgressWeight = 4},
-        New LoaderDownload("下载更新文件", New List(Of NetFile)) With {.ProgressWeight = 6},
-        New LoaderTask(Of List(Of NetFile), Boolean)("二次启动尝试", AddressOf InitSecond) With {.ProgressWeight = 3},
-        New LoaderTask(Of Boolean, Boolean)("创建请求核心房间", AddressOf InitRequest) With {.ProgressWeight = 2}
-    })
-    '判断端口是否被占用
-    Private Shared Sub InitPortCheck()
-        '检查协议
-        If Not Setup.Get("LinkEula") Then
-Reopen:
-            Select Case MyMsgBox("PCL 的联机服务由速聚授权提供。" & vbCrLf & "在使用前,你需要同意速聚的用户服务协议和隐私政策。", "协议授权", "同意", "拒绝", "查看用户服务协议和隐私政策")
-                Case 1
-                    Setup.Set("LinkEula", True)
-                Case 2
-                    Throw New Exception("你拒绝了用户服务协议……")
-                Case 3
-                    OpenWebsite("https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzkxMTMyODk3Mg==&action=getalbum&album_id=2585385685407514625&scene=173&from_msgid=2247483720&from_itemidx=1&count=3&nolastread=1#wechat_redirect")
-                    GoTo Reopen
-            End Select
-        End If
-        '先掐死
-        IoiStop(True)
-        '检查端口
-        Dim HasConflict As Boolean = False
-        For Each Port In Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()
-            If Port.Port <> 55555 AndAlso Port.Port <> 55557 Then Continue For
-            Log("[IOI] 发现端口 " & Port.Port & " 被占用")
-            HasConflict = True
-        Next
-        If Not HasConflict Then Exit Sub
-        '对应的报错
-        For Each Line As String In ShellAndGetOutput("netstat", "-ano", 30000).Split({vbCrLf}, StringSplitOptions.RemoveEmptyEntries)
-            If Not (Line.Contains("127.0.0.1:55555") OrElse Line.Contains("127.0.0.1:55557")) Then Continue For
-            Dim ProcessName As String
-            Try
-                ProcessName = Process.GetProcessById(Line.Split(" ").Last).ProcessName
-            Catch ex As Exception
-                Log(ex, "获取占用端口的进程信息失败,假定进程已结束")
-                Continue For
-            End Try
-            If ProcessName = "联机模块" Then
-                Throw New Exception("由于一个已知问题,请在重启电脑后再尝试使用联机功能")
-            ElseIf ProcessName = "Idle" Then
-                '不知道为啥反正又没占用了
-                Log("[IOI] 未发现占用此端口的程序,继续执行")
-            Else
-                Throw New Exception("端口被程序 " & ProcessName & " 占用,无法启动联机模块,请在任务管理器关闭此程序后再试")
-            End If
-        Next
-    End Sub
-    '下载更新前尝试启动
-    Private Shared Sub InitFirst(Task As LoaderTask(Of Integer, List(Of NetFile)))
-        '初次启动尝试
-        If IoiVersion <> Setup.Get("LinkIoiVersion") Then
-            Log("[IOI] 设置版本强制要求联机模块更新")
-            Setup.Set("LinkIoiVersion", IoiVersion)
-        ElseIf File.Exists(PathIoi) Then
-            Task.Progress = 0.2
-            CheckFirewall()
-            If IoiStart() Then
-                '已完成启动与初始化
-                Task.Output = New List(Of NetFile)
-                Exit Sub
-            Else
-                '需要更新
-                IoiStop(True)
-                File.Delete(PathIoi)
-                GoTo StartDownload
-            End If
-            '如果抛出异常则直接使加载器失败
-        End If
-        '开始下载
-StartDownload:
-        Task.Progress = 0.8
-        Log("[IOI] 需要下载联机模块")
-        If File.Exists(PathTemp & "联机模块.zip") Then File.Delete(PathTemp & "联机模块.zip")
-        Task.Output = New List(Of NetFile) From {New NetFile(
-            {"https://gitcode.net/to/cato_bin/-/raw/master/ioi_v2_x" & If(Is32BitSystem, 32, 64) & ".zip",
-             "http://mirror.hiper.cn.s2.the.bb/ioi_v2_x" & If(Is32BitSystem, 32, 64) & ".zip",
-             "http://mirror.hiper.cn.s3.the.bb:175/ioi_v2_x" & If(Is32BitSystem, 32, 64) & ".zip",
-             "https://pcl2-server-1253424809.file.myqcloud.com/link/ioi_v2_x" & If(Is32BitSystem, 32, 64) & ".zip{CDN}"},
-            PathTemp & "联机模块.zip")}
-    End Sub
-    '下载更新后尝试启动
-    Private Shared Sub InitSecond(Task As LoaderTask(Of List(Of NetFile), Boolean))
-        '若首次尝试已经成功,则直接跳过
-        If IoiState = LoadState.Finished Then Exit Sub
-        '解压更新包
-        Log("[IOI] 解压联机模块以完成下载")
-        If File.Exists(PathTemp & "ioi.exe") Then File.Delete(PathTemp & "ioi.exe")
-        If File.Exists(PathIoi) Then File.Delete(PathIoi)
-        ExtractFile(PathTemp & "联机模块.zip", PathTemp)
-        File.Delete(PathTemp & "联机模块.zip")
-        CopyFile(PathTemp & "ioi.exe", PathIoi)
-        File.Delete(PathTemp & "ioi.exe")
-        Task.Progress = 0.4
-        '再次尝试启动
-        CheckFirewall()
-        If IoiStart() Then Exit Sub
-        IoiStop(True)
-        Throw New Exception("联机模块初始化失败")
-    End Sub
-    '在 IOI 启动后建立 55557 房间
-    Private Shared Sub InitRequest()
-        Try
-            Dim Result As JObject = GetJson(NetRequestOnce("http://127.0.0.1:55555/api/port?proto=tcp&port=55557" & "&password=" & IoiPassword, "PUT", "", "", 100000))
-            If Result("msg") IsNot Nothing Then Throw New InvalidOperationException(Result("msg").ToString)
-        Catch ex As InvalidOperationException
-            'API 返回的错误
-            Log("创建请求核心房间失败:" & ex.Message, LogLevel.Msgbox)
-        Catch ex As Exception
-            '常规错误
-            Log(ex, "创建请求核心房间失败", LogLevel.Msgbox)
-        End Try
-    End Sub
-
-    '检查防火墙权限,并添加对应的权限
-    Private Shared Sub CheckFirewall()
-        Try
-            Log("[IOI] Windows 防火墙:检测开始")
-            If Not PageLinkLeft.FirewallIsBlock(PathIoi) Then Exit Try
-            If PageLinkLeft.FirewallPolicy.CurrentProfile.ExceptionsNotAllowed Then
-                '禁止白名单
-                MyMsgBox("由于 Windows 防火墙阻止了所有传入连接,PCL 无法获取防火墙通行权限。" & vbCrLf &
-                         "联机会有很大概率失败,就算连上了,延迟也会变高……" & vbCrLf & vbCrLf &
-                         "请先关闭 Windows 防火墙中的 " & vbLQ & "阻止所有传入连接" & vbRQ & " 选项,然后重启 PCL。", "遭到防火墙拦截")
-            ElseIf IsAdmin() Then
-                '有管理员权限
-                Log("[IOI] Windows 防火墙:尝试添加防火墙通行权限")
-                Try
-                    PageLinkLeft.FirewallAddAuthorized("Plain Craft Launcher 启动器(IOI 联机模块)", PathIoi)
-                Catch ex As Exception
-                    Log(ex, "无法将联机模块添加至防火墙白名单,可能导致联机失败", LogLevel.Msgbox)
-                End Try
-            Else
-                '无管理员权限
-                If MyMsgBox("由于你开启了 Windows 防火墙,PCL 需要获取防火墙通行权限。" & vbCrLf & vbCrLf &
-                            "若继续,PCL 将尝试以管理员权限重新启动。" & vbCrLf &
-                            "若拒绝,联机模块可能会被防火墙拦截,联机会有很大概率失败。", "需要管理员权限", "继续", "拒绝") = 1 Then
-                    Log("[IOI] Windows 防火墙:尝试提升权限")
-                    If RunAsAdmin("--link ioi") Then
-                        FrmMain.EndProgram(False) '已重新运行
-                    Else
-                        Hint("获取管理员权限失败,请尝试右键 PCL,选择 " & vbLQ & "以管理员身份运行" & vbRQ & ",然后再进入联机页面!", HintType.Critical)
-                    End If
-                Else
-                    Hint("在没有防火墙权限的情况下尝试联机,很可能会导致联机失败!", HintType.Critical)
-                End If
-            End If
-            Log("[IOI] Windows 防火墙:无防火墙通行权限")
-        Catch ex As Exception
-            Log(ex, "无法检测防火墙状态,可能导致联机失败", LogLevel.Msgbox)
-        End Try
-    End Sub
-
-#End Region
-
 #Region "进程管理"
 
     Private Shared IoiId As String, IoiPassword As String
@@ -209,38 +15,7 @@ StartDownload:
     ''' 若 Ioi 正在运行,则结束 Ioi 进程,同时初始化状态数据。返回是否关闭了对应进程。
     ''' </summary>
     Public Shared Function IoiStop(SleepWhenKilled As Boolean) As Boolean
-        IoiStop = False
-        '发送断开信息
-        Try
-            If IoiProcess IsNot Nothing AndAlso Not IoiProcess.HasExited Then
-                For i = 0 To UserList.Count - 1
-                    If i > UserList.Count - 1 Then Exit For
-                    Dim User = UserList.Values(i)
-                    SendDisconnectRequest(User)
-                Next
-            End If
-        Catch ex As Exception
-            Log(ex, "结束 IOI 进程时发送 Disconnect 信息失败")
-        End Try
-        '关闭所有 Ioi 进程
-        For Each ProcessObject In Process.GetProcesses
-            If ProcessObject.ProcessName <> "IOI 联机模块" Then Continue For
-            IoiStop = True
-            Try
-                ProcessObject.Kill()
-                Log("[IOI] 已关闭联机模块:" & ProcessObject.Id)
-                If SleepWhenKilled Then Thread.Sleep(3000) '等待 3 秒确认进程已退出
-            Catch ex As Exception
-                Log(ex, "关闭联机模块失败(" & ProcessObject.Id & ")")
-            End Try
-        Next
-        '初始化
-        IoiProcess = Nothing
-        IoiId = Nothing
-        IoiPassword = Nothing
-        IoiState = LoadState.Waiting
-        UserList = New Dictionary(Of String, LinkUserIoi)
-        RoomListForMe = New List(Of RoomEntry)
+        Return False
     End Function
     ''' <summary>
     ''' 启动 Ioi,并等待初始化完成后退出运行,同时更新 IoiId 与 IoiPassword。
@@ -248,130 +23,11 @@ StartDownload:
     ''' 若 Ioi 正在运行,则会先停止其运行。
     ''' </summary>
     Public Shared Function IoiStart() As Boolean
-        IoiStop(True)
-        Log("[IOI] 启动联机模块进程")
-        Dim Info = New ProcessStartInfo With {
-            .FileName = PathIoi,
-            .UseShellExecute = False,
-            .CreateNoWindow = True,
-            .RedirectStandardError = True,
-            .RedirectStandardOutput = True,
-            .WorkingDirectory = PathTemp
-        }
-        IoiProcess = New Process() With {.StartInfo = Info}
-        Using outputWaitHandle As New AutoResetEvent(False)
-            Using errorWaitHandle As New AutoResetEvent(False)
-                AddHandler IoiProcess.OutputDataReceived, Function(sender, e)
-                                                              Try
-                                                                  If e.Data Is Nothing Then
-                                                                      outputWaitHandle.[Set]()
-                                                                  Else
-                                                                      IoiLogLine(e.Data)
-                                                                  End If
-                                                              Catch ex As ObjectDisposedException
-                                                              Catch ex As Exception
-                                                                  Log(ex, "读取联机模块信息失败")
-                                                                  IoiState = LoadState.Failed
-                                                              End Try
-                                                              Return Nothing
-                                                          End Function
-                AddHandler IoiProcess.ErrorDataReceived, Function(sender, e)
-                                                             Try
-                                                                 If e.Data Is Nothing Then
-                                                                     errorWaitHandle.[Set]()
-                                                                 Else
-                                                                     IoiLogLine(e.Data)
-                                                                 End If
-                                                             Catch ex As ObjectDisposedException
-                                                             Catch ex As Exception
-                                                                 Log(ex, "读取联机模块错误信息失败")
-                                                                 IoiState = LoadState.Failed
-                                                             End Try
-                                                             Return Nothing
-                                                         End Function
-                IoiProcess.Start()
-                IoiState = LoadState.Loading
-                IoiProcess.BeginOutputReadLine()
-                IoiProcess.BeginErrorReadLine()
-                '等待
-                Do Until IoiProcess.HasExited OrElse IoiState <> LoadState.Loading
-                    Thread.Sleep(10)
-                Loop
-                '输出
-                If IoiState = LoadState.Finished Then
-                    Log("[IOI] 联机模块启动成功")
-                    Return True
-                Else
-                    Throw New Exception("联机模块启动失败")
-                End If
-                'Select Case IoiState
-                '    Case LoadState.Finished
-                '        Log("[IOI] 联机模块启动成功")
-                '        Return True
-                '    Case LoadState.Aborted
-                '        Log("[IOI] 联机模块要求更新")
-                '        Return False
-                '    Case LoadState.Failed
-                '        Log("[IOI] 联机模块启动出现异常")
-                '        Return False
-                '    Case Else 'LoadState.Loading
-                '        Throw New Exception("联机模块启动失败,请检查你的网络连接")
-                'End Select
-            End Using
-        End Using
+        Return False
     End Function
 
     'Ioi 日志
     Private Shared Sub IoiLogLine(Content As String)
-
-        '初始化
-        If Content.Contains(" > http://127.0.0.1:55555/") Then Exit Sub
-        If Content.Contains("All done") Then
-            If IoiId IsNot Nothing AndAlso IoiPassword IsNot Nothing Then
-                IoiState = LoadState.Finished
-            Else
-                Log("[IOI] 联机模块汇报初始化完成,但未提供账户信息")
-                IoiState = LoadState.Failed
-            End If
-            Exit Sub
-        End If
-        If Content.Contains("Password :: ") Then
-            IoiPassword = Content.Split({"Password :: "}, StringSplitOptions.None)(1)
-            Exit Sub '为保证安全不记录密码到 Log
-        End If
-
-        '初始化
-        If Content.Contains("ID :: ") Then IoiId = Content.Split({"ID :: "}, StringSplitOptions.None)(1)
-        If Content.Contains("Initialization failed") OrElse Content.Contains("The version is ") Then IoiState = LoadState.Aborted
-
-        '系统回应
-        If Content.Contains("'portssub' from ") Then LastPortsId = Content.Split(" ").Last
-        If Content.Contains("Listening tcp ") Then
-            If UserList.ContainsKey(LastPortsId) Then
-                UserList(LastPortsId).Ports(Content.Split(" ").Last) = RegexSeek(Content, "(?<=Listening tcp )[^:]+")
-            Else
-                Log("[IOI] 未在列表中的用户出现意料外的连接信息")
-                NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & LastPortsId & "&password=" & IoiPassword, "DELETE", "", "", 500)
-            End If
-        End If
-
-        '断开连接
-        If Content.Contains("Closed tcp connection from ") AndAlso Content.Contains(":55557") Then
-            For i = 0 To UserList.Count - 1
-                Dim User = UserList.Values(i)
-                If Not Content.Contains(User.Ports(55557)) OrElse User.IsDisposed Then Continue For
-                Log("[IOI] 检测到 55557 端口断开(" & User.DisplayName & ")")
-                UserRemove(User, True)
-                Exit For
-            Next
-        End If
-
-        '写入日志
-        If Not Content.Contains("read /dev/stdin: The handle is invalid.") AndAlso LogLinesCount < If(ModeDebug, 10000, 1000) Then
-            LogLinesCount += 1
-            Log("[IOI] " & Content)
-        End If
-
     End Sub
     Private Shared LogLinesCount As Integer = 0
     Private Shared LastPortsId As String = "" '上一个收到 portssub 的 ID,用于记录端口
@@ -384,94 +40,10 @@ StartDownload:
     Private Shared UserListIdentifyCache As String = ""
     Private Shared RoomListIdentifyCache As String = ""
     Public Sub RefreshUi()
-        Try
-            '确认用户列表缓存
-            Dim UserListIdentify As String = Join(UserList, vbCrLf)
-            If UserListIdentify <> UserListIdentifyCache Then
-                UserListIdentifyCache = UserListIdentify
-                '刷新列表项
-                PanUserList.Children.Clear()
-                For i = 0 To UserList.Count - 1
-                    If i > UserList.Count - 1 Then Exit For
-                    PanUserList.Children.Add(UserList.Values(i).ToListItem)
-                Next
-                '刷新列表标题
-                CardUser.Title = "已连接的玩家 (" & PanUserList.Children.Count & ")"
-            End If
-            '刷新用户列表项
-            For Each UserItem As MyListItem In PanUserList.Children
-                CType(UserItem.Tag, LinkUserIoi).RefreshUi(UserItem)
-            Next
-            '确认房间列表缓存
-            Dim RoomListIdentify As String = Join(GetRoomList(), vbCrLf)
-            If RoomListIdentify <> RoomListIdentifyCache Then
-                RoomListIdentifyCache = RoomListIdentify
-                '刷新列表项
-                PanRoom.Children.Clear()
-                For Each Room In GetRoomList()
-                    PanRoom.Children.Add(Room.ToListItem)
-                Next
-            End If
-            '刷新房间列表项
-            For Each RoomItem As MyListItem In PanRoom.Children
-                CType(RoomItem.Tag, RoomEntry).RefreshUi(RoomItem)
-            Next
-            '刷新操作提示
-            If UserList.Count = 0 Then
-                If RoomListForMe.Count = 0 Then
-                    LabHint.Text = "若想创建房间,请点击创建房间按钮,并按说明进行操作。" & vbCrLf & "若想加入他人的房间,请点击建立连接,然后输入对方的联机码。"
-                Else
-                    LabHint.Text = "若想让其他人加入你的房间,请点击复制联机码,然后让你的朋友输入你的联机码以建立连接。"
-                End If
-            Else
-                If GetRoomList.Count = 0 Then
-                    LabHint.Text = "若想创建房间,请点击创建房间按钮,然后输入对局域网开放后 MC 显示的端口号,或本地服务端的端口号。"
-                ElseIf RoomListForMe.Count = 0 Then
-                    '已有连接,本人没有房间,对方有房间
-                    LabHint.Text = "若想加入某个房间,直接点击该房间即可获取说明。"
-                Else
-                    '已有连接,且本人有房间
-                    LabHint.Text = "指向你所创建的房间,能在右侧找到修改房间名称、关闭房间等选项。" &
-                                   If(RoomListForMe.Count <> GetRoomList.Count, vbCrLf & "若想加入其他人的房间,直接点击该房间即可获取说明。", "")
-                End If
-            End If
-        Catch ex As Exception
-            Log(ex, "联机模块 UI 时钟运行失败", LogLevel.Feedback)
-        End Try
     End Sub
 
     '工作线程刷新
     Public Sub RefreshWorker()
-        Try
-            '检测退出
-            If InitLoader.State = LoadState.Finished AndAlso IoiProcess.HasExited Then
-                Log("[IOI] 联机模块出现异常!", LogLevel.Hint)
-                ModuleStopManually()
-            End If
-            '检测用户
-            For i = 0 To UserList.Values.Count - 1
-                If i > UserList.Values.Count - 1 Then Exit For
-                Dim User = UserList.Values(i)
-                If User.Progress < 1 Then Continue For '跳过连接中的用户
-                '发送心跳包
-                If Date.Now - User.LastSend > New TimeSpan(0, 0, RandomInteger(50, 30)) Then
-                    RunInNewThread(Sub()
-                                       Try
-                                           SendUpdateRequest(User, 1)
-                                       Catch ex As Exception
-                                           Log(ex, "心跳包发送失败(" & User.DisplayName & ")", LogLevel.Normal)
-                                       End Try
-                                   End Sub, "Link Heartbeat " & User.Id)
-                End If
-                '检测被动离线
-                If Date.Now - User.LastReceive > New TimeSpan(0, 2, 0) Then
-                    SendDisconnectRequest(User)
-                    Hint("与 " & User.DisplayName & " 的连接已中断!", HintType.Critical)
-                End If
-            Next
-        Catch ex As Exception
-            Log(ex, "联机模块工作时钟运行失败", LogLevel.Feedback)
-        End Try
     End Sub
 
 #End Region
@@ -482,65 +54,6 @@ StartDownload:
     ''' 发送 Portsub 请求并等待获取控制台端口。进度将从 0 变化至 80%。
     ''' </summary>
     Private Shared Sub SendPortsubRequest(User As LinkUserIoi)
-        Log("[IOI] 尝试建立连接:" & User.Id)
-        '向对方发送 portsub 请求
-        Dim Retry As Integer = 0
-RetryLink:
-        Dim Result As JObject = GetJson(NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & User.Id & "&password=" & IoiPassword, "PUT", "", "", 100000))
-        '失败与重试
-        If Result("msg") IsNot Nothing Then
-            Dim ErrorMessage As String = Result("msg").ToString
-            Select Case ErrorMessage
-                Case "failed to find any peer in table"
-                    Retry += 1
-                    ErrorMessage = "我方网络环境不佳,连接失败。" '初始化对等机列表超时
-                    Log("[IOI] 尝试建立连接结果:未找到对等机,第 " & Retry & " 级重试")
-                Case "routing: not found"
-                    Retry += 4
-                    ErrorMessage = "我方或对方网络环境不佳,或对方已关闭联机模块,未找到路由。" '未知情况
-                    Log("[IOI] 尝试建立连接结果:无法连接到路由,第 " & Retry & " 级重试")
-                Case "you are already connected to specified host"
-                    If User.IsDisposed Then Throw New ThreadInterruptedException("用户对象已被释放")
-                    Log("[IOI] 尝试建立连接结果:已与对方连接")
-                    GoTo Done
-                    'Retry += 1
-                    'NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & User.Id & "&password=" & IoiPassword, "DELETE", "", "", 100000)
-                    'ErrorMessage = "已与对方连接。" '双向连接偶尔导致
-                    'Log("[IOI] 已与对方连接,尝试中断现有连接,第 " & Retry & " 级重试")
-                Case "dial backoff"
-                    Retry += 20
-                    ErrorMessage = "对方网络环境不佳,或对方已关闭联机模块。请尝试让对方主动连接你,而不是你去连接对方。" 'NAT2 连 NAT4 导致
-                    Log("[IOI] 尝试建立连接结果:NAT 异常,第 " & Retry & " 级重试")
-                Case Else
-                    If ErrorMessage.Contains("all dials failed") Then
-                        Retry += 8
-                        ErrorMessage = "我方或对方网络环境不佳,或对方已关闭联机模块,连接失败。" '网络不佳,或对方已关闭模块
-                        Log("[IOI] 尝试建立连接结果:连接失败,第 " & Retry & " 级重试")
-                    Else
-                        Retry += 8
-                        Log("[IOI] 尝试建立连接结果:未知错误(" & ErrorMessage & "),第 " & Retry & " 级重试")
-                    End If
-            End Select
-            If User.IsDisposed Then Throw New ThreadInterruptedException("用户对象已被释放")
-            If Retry <= 64 Then
-                User.Progress = Retry * 0.01 + 0.05
-                Thread.Sleep(3000)
-                GoTo RetryLink
-            Else
-                Throw New InvalidOperationException(ErrorMessage)
-            End If
-        End If
-        Log("[IOI] 尝试建立连接结果:成功")
-        '等待获取对方端口
-Done:
-        Dim WaitCount As Integer = 0
-        Do Until User.Ports.ContainsKey(55555) AndAlso User.Ports.ContainsKey(55557)
-            WaitCount += 1
-            User.Progress = 0.7 + WaitCount / 200 * 0.1 '70% ~ 80%
-            If WaitCount = 100 Then Throw New Exception("连接超时,请尝试重新连接(未收到端口回报)!")
-            Thread.Sleep(150)
-        Loop
-        User.Progress = 0.8
     End Sub
 
     ''' <summary>
@@ -584,20 +97,6 @@ Done:
     ''' 尝试发送断开请求,并将其从用户列表中移除。
     ''' </summary>
     Private Shared Sub SendDisconnectRequest(User As LinkUserIoi, Optional Message As String = Nothing, Optional IsError As Boolean = False)
-        Dim RawJson As New JObject()
-        RawJson("id") = IoiId
-        RawJson("type") = "disconnect"
-        If Message IsNot Nothing Then
-            RawJson("message") = Message
-            RawJson("isError") = IsError
-        End If
-        Try
-            User.Send(RawJson)
-            Thread.Sleep(50)
-        Catch
-        End Try
-        NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & User.Id & "&password=" & IoiPassword, "DELETE", "", "", 500)
-        User.Dispose()
     End Sub
 
 #End Region
@@ -606,29 +105,9 @@ Done:
 
     '刷新连接
     Private Shared Sub BtnListRefresh_Click(sender As MyIconButton, e As EventArgs)
-        Dim User As LinkUserIoi = sender.Tag
-        User.PingRecord.Clear()
-        FrmLinkIoi.RefreshUi()
-        RunInThread(Sub()
-                        Try
-                            SendUpdateRequest(User, 1)
-                        Catch ex As Exception
-                            If InitLoader.State = LoadState.Finished Then Log(ex, "刷新与 " & User.DisplayName & " 的连接失败", LogLevel.Hint)
-                        End Try
-                    End Sub)
     End Sub
     '断开连接
     Private Shared Sub BtnListDisconnect_Click(sender As MyIconButton, e As EventArgs)
-        Dim User As LinkUserIoi = sender.Tag
-        sender.IsEnabled = False
-        RunInThread(Sub()
-                        If User.Progress < 1 AndAlso User.RelativeThread IsNot Nothing AndAlso User.RelativeThread.IsAlive Then
-                            NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & User.Id & "&password=" & IoiPassword, "DELETE", "", "", 500)
-                            User.Dispose()
-                        Else
-                            SendDisconnectRequest(User, GetPlayerName() & " 主动断开了连接!")
-                        End If
-                    End Sub)
     End Sub
     '复制联机码
     Public Shared Sub BtnLeftCopy_Click() Handles BtnLeftCopy.Click
@@ -680,46 +159,10 @@ Done:
 
 #Region "请求核心"
 
-    Private Shared Listener As Socket = Nothing
     ''' <summary>
     ''' 启动 Socket 监听核心。
     ''' </summary>
     Public Shared Sub StartSocketListener()
-        If Listener IsNot Nothing Then Exit Sub '已经启动过了
-        Listener = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) With {.ReceiveTimeout = 1000000000, .SendTimeout = 1000000000}
-        Listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, True)
-        Try
-            Listener.Bind(New IPEndPoint(IPAddress.Any, 55557))
-        Catch ex As Exception
-            Log(ex, "初次启动 Socket 监听核心失败,开始重试")
-            Thread.Sleep(1000)
-            Listener.Bind(New IPEndPoint(IPAddress.Any, 55557))
-        End Try
-        Listener.Listen(100)
-        Log("[IOI] 已启动 Socket 监听核心")
-        RunInNewThread(Sub()
-                           While True
-                               Try
-                                   '获取新的 Socket
-                                   Dim bytes(1024) As Byte
-                                   Dim ClientSocket As Socket = Listener.Accept()
-                                   '获取初始输入信息
-                                   Dim RequestInput As String = Encoding.UTF8.GetString(bytes, 0, ClientSocket.Receive(bytes))
-                                   If Not RequestInput.StartsWith("PCL - ") Then Exit Sub
-                                   Dim RawData As String
-                                   Try
-                                       RawData = SecretDecrypt(WebUtility.UrlDecode(RequestInput.Substring(6)))
-                                       Log("[IOI] 接收到新的 Socket:" & RawData)
-                                       PageLinkIoi.ReceiveJson(GetJson(RawData), ClientSocket)
-                                   Catch ex As Exception
-                                       Log(ex, "新的 Socket 处理出错(内容:" & RequestInput & ")")
-                                   End Try
-                               Catch ex As Exception
-                                   Log(ex, "Socket 监听出错")
-                                   Thread.Sleep(3000) '防止死循环出错
-                               End Try
-                           End While
-                       End Sub, "Socket Listener")
     End Sub
 
 #End Region
@@ -738,40 +181,9 @@ Done:
         '请求管理
         Public Socket As Socket = Nothing
         Public Sub Send(Request As JObject)
-            If Socket Is Nothing Then Throw New Exception("该用户尚未绑定 Socket")
-            Dim Data As String = Request.ToString(Newtonsoft.Json.Formatting.None)
-            If ModeDebug Then Log("[IOI] 发送联机数据包(" & DisplayName & "):" & Data)
-            Data = "PCL - " & WebUtility.UrlEncode(SecretEncrypt(Data))
-            Me.LastSend = Date.Now
-            Socket.Send(Encoding.UTF8.GetBytes(Data))
         End Sub
         Public ListenerThread As Thread = Nothing
         Public Sub StartListener()
-            If ListenerThread IsNot Nothing Then Exit Sub
-            '启动监听
-            ListenerThread = RunInNewThread(Sub()
-                                                Try
-                                                    Dim bytes(1024) As Byte
-                                                    While PageLinkIoi.UserList.ContainsValue(Me)
-                                                        Dim RequestInput As String = Encoding.UTF8.GetString(bytes, 0, Socket.Receive(bytes))
-                                                        If Not RequestInput.StartsWith("PCL - ") Then Exit Sub
-                                                        Dim RawData As String = SecretDecrypt(WebUtility.UrlDecode(RequestInput.Substring(6)))
-                                                        If ModeDebug Then Log("[IOI] 接收联机数据包(" & DisplayName & "):" & RawData)
-                                                        PageLinkIoi.ReceiveJson(GetJson(RawData)) '调用去实际的联机模块
-                                                    End While
-                                                Catch ex As ThreadInterruptedException
-                                                    Log("[IOI] 用户监听已中断(" & DisplayName & ")")
-                                                Catch ex As Exception
-                                                    If IsDisposed Then Exit Sub
-                                                    If ex.GetType.Equals(GetType(SocketException)) AndAlso CType(ex, SocketException).SocketErrorCode = 10053 Then
-                                                        Log("[IOI] 客户端已关闭(" & DisplayName & ")")
-                                                        PageLinkIoi.UserRemove(CType(Me, PageLinkIoi.LinkUserIoi), True)
-                                                        Exit Sub
-                                                    End If
-                                                    Log(ex, "用户监听出错(" & DisplayName & ")")
-                                                    PageLinkIoi.UserRemove(CType(Me, PageLinkIoi.LinkUserIoi), True)
-                                                End Try
-                                            End Sub, "Link Listener " & DisplayName)
         End Sub
         Public Sub BindSocket(Socket As Socket)
             If Me.Socket IsNot Nothing Then Throw New Exception("该用户已经绑定了 Socket")
@@ -975,135 +387,12 @@ Done:
 
     '正向与反向连接
     Public Shared Sub BtnLeftCreate_Click() Handles BtnLeftCreate.Click
-        ''获取信息
-        'Dim Code As String = MyMsgBoxInput("", New ObjectModel.Collection(Of Validate) From {New ValidateLength(9, 99999)}, "", "输入对方的联机码", "确定", "取消")
-        'If Code Is Nothing Then Exit Sub
-        ''检查
-        'If Code.StartsWith("P") AndAlso Code.Length < 48 Then
-        '    Hint("你输入的可能是 HiPer 的联机码,请在左侧的联机方式中选择 HiPer!", HintType.Critical) : Exit Sub
-        'End If
-        'Dim Id As String, DisplayName As String
-        'Try '解密失败检查
-        '    Id = "12D3" & Code.Substring(0, 48)
-        '    DisplayName = SecretDecrypt(Code.Substring(48))
-        'Catch
-        '    Hint("你输入的联机码有误!", HintType.Critical)
-        '    Exit Sub
-        'End Try
-        'If Id = IoiId Then '自我连接检查
-        '    Hint("我连我自己?搁这卡 Bug 呢?", HintType.Critical)
-        '    Exit Sub
-        'End If
-        ''开始
-        'Dim User As New LinkUserIoi(Id, DisplayName)
-        'User.RelativeThread = RunInNewThread(Sub()
-        '                                         Dim WaitCount As Integer = 0
-        '                                         Try
-        '                                             '加入列表
-        '                                             If UserList.ContainsKey(Id) Then
-        '                                                 Hint(UserList(Id).DisplayName & " 已在列表中,无需再次连接!")
-        '                                                 Exit Sub
-        '                                             Else
-        '                                                 UserList.Add(Id, User)
-        '                                             End If
-        '                                             '发送 portsub 请求(0% -> 80%)
-        '                                             SendPortsubRequest(User)
-        '                                             '构建 Socket(81% -> 82%)
-        '                                             Dim ClientSocket As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) With {.ReceiveTimeout = 1000000000, .SendTimeout = 1000000000}
-        '                                             ClientSocket.Connect(New IPEndPoint(IPAddress.Parse(User.Ports(55557)), 55557))
-        '                                             User.BindSocket(ClientSocket)
-        '                                             User.Progress = 0.82
-        '                                             '发送 connect 请求(83% -> 85%)
-        '                                             SendConnectRequest(User)
-        '                                             User.Progress = 0.85
-        '                                             '等待对方向自己请求
-        '                                             Log("[IOI] 加入成功,等待反向请求")
-        '                                             Do While User.Progress < 0.9999
-        '                                                 User.Progress += 0.0002
-        '                                                 If User.Progress > 0.98 AndAlso User.Progress < 0.9999 Then Throw New Exception("对方未回应连接请求!")
-        '                                                 Thread.Sleep(100)
-        '                                             Loop
-        '                                             Hint("已连接到 " & User.DisplayName & "!", HintType.Finish)
-        '                                         Catch ex As ThreadInterruptedException
-        '                                             Log("[IOI] 已中断主动发起的连接(" & User.DisplayName & ")")
-        '                                         Catch ex As InvalidOperationException
-        '                                             'API 返回的错误
-        '                                             NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & Id & "&password=" & IoiPassword, "DELETE", "", "", 500)
-        '                                             User.Dispose()
-        '                                             If InitLoader.State = LoadState.Finished Then Log("与 " & DisplayName & " 建立连接失败:" & ex.Message, LogLevel.Msgbox)
-        '                                         Catch ex As Exception
-        '                                             '常规错误
-        '                                             NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & Id & "&password=" & IoiPassword, "DELETE", "", "", 500)
-        '                                             User.Dispose()
-        '                                             If ex.InnerException IsNot Nothing AndAlso TypeOf ex.InnerException Is ThreadInterruptedException Then Exit Sub
-        '                                             If InitLoader.State = LoadState.Finished Then Log(ex, "与 " & DisplayName & " 建立连接失败", LogLevel.Msgbox)
-        '                                         End Try
-        '                                     End Sub, "Link Create " & DisplayName)
     End Sub
     Private Shared Sub SendPortsubBack(User As LinkUserIoi, TargetVersion As Integer)
-        Try
-            SendPortsubRequest(User)
-            User.Progress = 0.9
-            If TargetVersion > RequestVersion Then
-                SendDisconnectRequest(User, "无法连接到 " & GetPlayerName() & ":对方的 PCL 版本过低!", True)
-                Throw New InvalidOperationException("你的 PCL 版本过低!")
-            ElseIf TargetVersion < RequestVersion Then
-                SendDisconnectRequest(User, "无法连接到 " & GetPlayerName() & ":你的 PCL 版本过低!", True)
-                Throw New InvalidOperationException("对方的 PCL 版本过低!")
-            Else
-                SendUpdateRequest(User, 1)
-                User.Progress = 1
-                Hint(User.DisplayName & " 已与你建立连接!")
-            End If
-        Catch ex As ThreadInterruptedException
-            Log("[IOI] 已中断被动建立的连接(" & User.DisplayName & ")")
-        Catch ex As InvalidOperationException
-            'API 返回的错误
-            NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & User.Id & "&password=" & IoiPassword, "DELETE", "", "", 500)
-            User.Dispose()
-            If InitLoader.State = LoadState.Finished Then Log("与 " & User.DisplayName & " 建立连接失败:" & ex.Message, LogLevel.Hint)
-        Catch ex As Exception
-            '常规错误
-            NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & User.Id & "&password=" & IoiPassword, "DELETE", "", "", 500)
-            User.Dispose()
-            If InitLoader.State = LoadState.Finished Then Log(ex, "与 " & User.DisplayName & " 建立连接失败", LogLevel.Hint)
-        End Try
     End Sub
 
     '创建房间
     Private Sub LinkCreate() Handles BtnCreate.Click
-        'If MyMsgBox("请先进入 MC 并暂停游戏,在暂停页面选择对局域网开放,然后在下一个窗口输入 MC 显示的端口号。" & vbCrLf & "若使用服务端开服,则直接在下一个窗口输入服务器配置中的端口号即可。", "提示", "继续", "取消") = 2 Then Exit Sub
-        ''获取端口号
-        'Dim Port As String = MyMsgBoxInput("", New ObjectModel.Collection(Of Validate) From {
-        '                                           New ValidateInteger(0, 65535),
-        '                                           New ValidateExceptSame({"55555", "55557"}, "端口不能为 %!"),
-        '                                           New ValidateExceptSame(RoomListForMe.Select(AddressOf RoomEntry.SelectPort), "端口 % 已创建过房间,请在删除该房间后继续!")
-        '                                       }, "", "输入端口号", "确定", "取消")
-        'If Port Is Nothing Then Exit Sub
-        ''获取显示名称
-        'Dim DisplayName As String = MyMsgBoxInput(GetPlayerName() & " 的房间 - " & Port, New ObjectModel.Collection(Of Validate) From {
-        '                                           New ValidateNullOrWhiteSpace(), New ValidateLength(1, 40), New ValidateFilter()
-        '                                       }, "", "输入房间名(建议包含游戏版本等信息)", "确定", "取消")
-        'If DisplayName Is Nothing Then Exit Sub
-        'DisplayName = DisplayName.Trim
-        ''开始
-        'RunInThread(Sub()
-        '                Try
-        '                    '请求
-        '                    Dim Result As JObject = GetJson(NetRequestOnce("http://127.0.0.1:55555/api/port?proto=tcp&port=" & Port & "&password=" & IoiPassword, "PUT", "", "", 100000))
-        '                    If Result("msg") IsNot Nothing Then Throw New InvalidOperationException(Result("msg").ToString)
-        '                    '成功
-        '                    RoomListForMe.Add(New RoomEntry(Port, DisplayName))
-        '                    Hint("房间 " & DisplayName & " 已创建!", HintType.Finish)
-        '                    SendUpdateRequestToAllUsers()
-        '                Catch ex As InvalidOperationException
-        '                    'API 返回的错误
-        '                    Log("创建房间失败:" & ex.Message, LogLevel.Msgbox)
-        '                Catch ex As Exception
-        '                    '常规错误
-        '                    Log(ex, "创建房间失败", LogLevel.Msgbox)
-        '                End Try
-        '            End Sub)
     End Sub
     Private Shared Sub SendUpdateRequestToAllUsers()
         For i = 0 To UserList.Count - 1
@@ -1119,18 +408,6 @@ Done:
     End Sub
     '修改房间名称
     Private Shared Sub BtnRoomEdit_Click(sender As MyIconButton, e As EventArgs)
-        'Dim Room As RoomEntry = sender.Tag
-        ''获取房间名
-        'Dim DisplayName As String = MyMsgBoxInput(Room.DisplayName, New ObjectModel.Collection(Of Validate) From {
-        '                                           New ValidateNullOrWhiteSpace(),
-        '                                           New ValidateLength(1, 40)
-        '                                       }, "", "输入房间名(建议包含游戏版本等信息)", "确定", "取消")
-        'If DisplayName Is Nothing Then Exit Sub
-        'DisplayName = DisplayName.Trim
-        ''修改
-        'Room.DisplayName = DisplayName
-        'FrmLinkIoi.RefreshUi()
-        'SendUpdateRequestToAllUsers()
     End Sub
     '加入房间
     Private Shared Sub BtnRoom_Click(sender As MyListItem, e As EventArgs)
@@ -1141,25 +418,6 @@ Done:
     End Sub
     '关闭房间
     Private Shared Sub BtnRoomClose_Click(sender As MyIconButton, e As EventArgs)
-        Dim Room As RoomEntry = sender.Tag
-        RunInThread(Sub()
-                        Try
-                            '远程移除
-                            Dim Result As JObject = GetJson(NetRequestOnce("http://127.0.0.1:55555/api/port?proto=tcp&port=" & Room.Port & "&password=" & IoiPassword, "DELETE", "", "", 100000))
-                            If Result("msg") IsNot Nothing Then Throw New InvalidOperationException(Result("msg").ToString)
-                            '本地移除
-                            RoomListForMe.Remove(Room)
-                            '成功
-                            RunInUi(Sub() FrmLinkIoi.RefreshUi())
-                            SendUpdateRequestToAllUsers()
-                        Catch ex As InvalidOperationException
-                            'API 返回的错误
-                            If InitLoader.State = LoadState.Finished Then Log("移除房间失败:" & ex.Message, LogLevel.Msgbox)
-                        Catch ex As Exception
-                            '常规错误
-                            If InitLoader.State = LoadState.Finished Then Log(ex, "移除房间失败", LogLevel.Msgbox)
-                        End Try
-                    End Sub)
     End Sub
 
     '获取数据包
@@ -1238,21 +496,9 @@ Done:
     ''' 从用户列表中移除一位用户。提示信息视作该用户主动离开。
     ''' </summary>
     Public Shared Sub UserRemove(User As LinkUserIoi, ShowLeaveMessage As Boolean)
-        If Not UserList.ContainsKey(User.Id) Then Exit Sub
-        If ShowLeaveMessage Then Hint(User.DisplayName & " 已离开!")
-        NetRequestOnce("http://127.0.0.1:55555/api/link?id=" & User.Id & "&password=" & IoiPassword, "DELETE", "", "", 500)
-        User.Dispose()
     End Sub
 
-    '关闭联机模块按钮
-    Private Shared Sub ModuleStateChanged(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) Handles InitLoader.OnStateChangedUi
-        If FrmLinkLeft IsNot Nothing Then CType(FrmLinkLeft.ItemIoi.Buttons(0), MyIconButton).Visibility = If(NewState = LoadState.Finished, Visibility.Visible, Visibility.Collapsed)
-    End Sub
     Public Shared Sub ModuleStopManually()
-        IoiStop(False)
-        InitLoader.Error = New Exception("联机模块已关闭,点击以重新启动")
-        InitLoader.State = LoadState.Failed
-        IoiState = LoadState.Failed
     End Sub
 
 End Class

+ 1 - 82
Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb

@@ -1,6 +1,4 @@
-Imports NetFwTypeLib
-
-Public Class PageLinkLeft
+Public Class PageLinkLeft
 
     Private IsLoad As Boolean = False
     Private IsPageSwitched As Boolean = False '如果在 Loaded 前切换到其他页面,会导致触发 Loaded 时再次切换一次
@@ -15,84 +13,6 @@ Public Class PageLinkLeft
         IsPageSwitched = False
     End Sub
 
-#Region "Windows 防火墙"
-
-    '为联机模块添加防火墙通行权限的函数库
-    '不加防火墙权限你要我怎么办,这联机根本连都连不上,球球你们别看到防火墙就说是后门了,真的很恼火……
-
-    ''' <summary>
-    ''' 获取某个文件是否被 Windows 防火墙阻止。
-    ''' </summary>
-    Public Shared Function FirewallIsBlock(FilePath As String) As Boolean
-        '获取配置
-        If FirewallPolicy Is Nothing Then
-            Try
-                FirewallPolicy = FirewallPolicyGet()
-            Catch ex As Exception
-                Log(ex, "Windows 防火墙:可能关闭得太死了,根本检测不到,笑死", LogLevel.Normal)
-                Return False
-            End Try
-        End If
-        '检查是否开启
-        If Not FirewallPolicy.CurrentProfile.FirewallEnabled Then
-            Log("[Link] Windows 防火墙:防火墙关闭")
-            Return False
-        End If
-        '检查是否允许例外
-        If FirewallPolicy.CurrentProfile.ExceptionsNotAllowed Then
-            Log("[Link] Windows 防火墙:防火墙开启,不允许例外")
-            Return True
-        End If
-        '检查白名单
-        Dim Target As String = FilePath.Replace(PathTemp.Split("\").First, "").ToLower
-        For Each App As INetFwAuthorizedApplication In FirewallPolicy.CurrentProfile.AuthorizedApplications
-            If ModeDebug Then Log("[Link] 防火墙白名单(" & App.Enabled & "):" & App.ProcessImageFileName)
-            If Not App.Enabled Then Continue For
-            '信标的奇妙盘符 E:0/xxxxx/xxx 导致误判……
-            Dim FileNameTrimmed As String = App.ProcessImageFileName.Replace(App.ProcessImageFileName.Split("\").First, "")
-            If App.ProcessImageFileName.ToLower.Contains(FilePath) Then
-                Log("[Link] Windows 防火墙:开启,已在白名单中(" & FilePath & ")")
-                Return False
-            End If
-        Next
-        Log("[Link] Windows 防火墙:开启,未在白名单中(" & FilePath & ")")
-        Return True
-    End Function
-    ''' <summary>
-    ''' 添加防火墙通行权限。需要管理员权限。
-    ''' </summary>
-    Public Shared Sub FirewallAddAuthorized(DisplayName As String, FilePath As String)
-        Log("[Link] Windows 防火墙:添加通行权限(" & FilePath & ")")
-        '获取配置
-        If FirewallPolicy Is Nothing Then FirewallPolicy = FirewallPolicyGet()
-        '添加项目
-        FirewallPolicy.GetProfileByType(NET_FW_PROFILE_TYPE_.NET_FW_PROFILE_DOMAIN).AuthorizedApplications.Add(New FirewallApp(DisplayName, FilePath))
-        FirewallPolicy.GetProfileByType(NET_FW_PROFILE_TYPE_.NET_FW_PROFILE_STANDARD).AuthorizedApplications.Add(New FirewallApp(DisplayName, FilePath))
-        FirewallPolicy.CurrentProfile.AuthorizedApplications.Add(New FirewallApp(DisplayName, FilePath))
-    End Sub
-    Private Class FirewallApp
-        Implements INetFwAuthorizedApplication
-        Public Property Name As String Implements INetFwAuthorizedApplication.Name
-        Public Property ProcessImageFileName As String Implements INetFwAuthorizedApplication.ProcessImageFileName
-        Public Property IpVersion As NET_FW_IP_VERSION_ = NET_FW_IP_VERSION_.NET_FW_IP_VERSION_ANY Implements INetFwAuthorizedApplication.IpVersion
-        Public Property Scope As NET_FW_SCOPE_ = NET_FW_SCOPE_.NET_FW_SCOPE_ALL Implements INetFwAuthorizedApplication.Scope
-        Public Property RemoteAddresses As String = "*" Implements INetFwAuthorizedApplication.RemoteAddresses
-        Public Property Enabled As Boolean = True Implements INetFwAuthorizedApplication.Enabled
-        Public Sub New(DisplayName As String, FilePath As String)
-            Name = DisplayName
-            ProcessImageFileName = FilePath
-        End Sub
-    End Class
-    '获取防火墙配置
-    Public Shared FirewallPolicy As INetFwPolicy = Nothing
-    Public Shared Function FirewallPolicyGet() As INetFwPolicy
-        Const CLSID_FIREWALL_MANAGER As String = "{304CE942-6E39-40D8-943A-B913C40C9CD4}"
-        Dim objType As Type = Type.GetTypeFromCLSID(New Guid(CLSID_FIREWALL_MANAGER))
-        Return TryCast(Activator.CreateInstance(objType), INetFwMgr).LocalPolicy
-    End Function
-
-#End Region
-
 #Region "页面切换"
 
     ''' <summary>
@@ -184,7 +104,6 @@ Public Class PageLinkLeft
         PageLinkHiper.ModuleStopManually()
     End Sub
     Private Sub BtnIoiStop_Loaded(sender As Object, e As RoutedEventArgs)
-        sender.Visibility = If(PageLinkIoi.InitLoader.State = LoadState.Finished, Visibility.Visible, Visibility.Collapsed)
     End Sub
     Private Sub BtnIoiStop_Click(sender As Object, e As EventArgs)
         PageLinkIoi.ModuleStopManually()

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

@@ -79,7 +79,7 @@
                             <RowDefinition Height="Auto" />
                         </Grid.RowDefinitions>
                         <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏窗口标题" Margin="0,0,25,0" />
-                        <local:MyTextBox x:Name="TextArgumentTitle" Grid.Row="0" Grid.ColumnSpan="2" Tag="LaunchArgumentTitle" Grid.Column="1" ToolTip="自定义游戏窗口的标题,若留空则不进行更改。&#xa;支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {name}:游戏版本名&#xa; · {time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号" HintText="默认" MaxLength="100" />
+                        <local:MyTextBox x:Name="TextArgumentTitle" Grid.Row="0" Grid.ColumnSpan="2" Tag="LaunchArgumentTitle" Grid.Column="1" ToolTip="自定义游戏窗口的标题,若留空则不进行更改。&#xa;支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号" HintText="默认" MaxLength="100" />
                         <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="自定义信息" Margin="0,0,25,0" />
                         <local:MyTextBox x:Name="TextArgumentInfo" Grid.Row="2" Grid.ColumnSpan="2" Tag="LaunchArgumentInfo" Text="Plain Craft Launcher 2" Grid.Column="1" ToolTip="该信息会显示在游戏主界面的左下角,与 F3 调试页面的左上角">
                             <local:MyTextBox.ValidateRules>
@@ -235,7 +235,7 @@
                     <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="LaunchAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如输入 --demo 则会以试玩模式启动游戏。" />
                     <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,0" />
                     <local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="LaunchAdvanceRun" Grid.Column="1" HintText=""
-                                     ToolTip="在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xa; · {path}:PCL 的 exe 文件所在的文件夹&#xa; · {minecraft}:.minecraft 文件夹&#xa; · {verpath}:版本文件夹(.minecraft\versions\版本名\)&#xa; · {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xa; · {java}:游戏运行时的 Java 文件夹&#xa;&#xa;除此之外,也支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {uuid}:玩家的 UUID&#xa; · {name}:游戏版本名&#xa; · {time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号&#xa;&#xa;例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}option.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。" />
+                                     ToolTip="在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xa; · {path}:PCL 的 exe 文件所在的文件夹&#xa; · {minecraft}:.minecraft 文件夹&#xa; · {verpath}:版本文件夹(.minecraft\versions\版本名\)&#xa; · {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xa; · {java}:游戏运行时的 Java 文件夹&#xa;&#xa;除此之外,也支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {uuid}:玩家的 UUID&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号&#xa;&#xa;例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}option.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
                     <local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="LaunchAdvanceRunWait" Visibility="Collapsed"
                                       Text="等待命令执行完成后再继续启动" Margin="0,8,0,7" />
                     <StackPanel Margin="0,12,0,4" Orientation="Horizontal" Grid.Row="6" Grid.ColumnSpan="2">

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

@@ -26,6 +26,7 @@
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
                             <RowDefinition Height="Auto" />
+                            <RowDefinition Height="Auto" />
                         </Grid.RowDefinitions>
                         <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Text="版本列表获取" Margin="0,0,25,0" />
                         <local:MyComboBox x:Name="ComboDownloadVersion" Grid.ColumnSpan="2" Tag="ToolDownloadVersion" Grid.Column="1">
@@ -55,6 +56,9 @@
                         <local:MyCheckBox Margin="0,2,0,4" Text="简化 Mod 加载器显示" Grid.Row="8" Height="22" Grid.ColumnSpan="2"
                                           x:Name="CheckDownloadIgnoreQuilt" Tag="ToolDownloadIgnoreQuilt" 
                                           ToolTip="下载 Mod 时,若该 Mod 支持 Forge 和 Fabric,则显示为支持任意 Mod 加载器。&#xa;例如,开启时 JEI 显示支持全版本,关闭后 JEI 就会显示仅支持 Forge / Fabric 全版本。" />
+                        <local:MyCheckBox Margin="0,2,0,4" Text="验证 SSL 证书" Grid.Row="9" Height="22" Grid.ColumnSpan="2"
+                                          x:Name="CheckDownloadCert" Tag="ToolDownloadCert" 
+                                          ToolTip="在进行连接时,检查 SSL 证书是否正常。&#xa;进行验证会提高安全性,但疑似会导致少部分玩家的微软登录失败。" />
                     </Grid>
                 </StackPanel>
             </local:MyCard>

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

@@ -58,6 +58,7 @@
         ComboDownloadTranslate.SelectedIndex = Setup.Get("ToolDownloadTranslate")
         CheckDownloadKeepModpack.Checked = Setup.Get("ToolDownloadKeepModpack")
         CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
+        CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert")
 
         'Minecraft 更新提示
         CheckUpdateRelease.Checked = Setup.Get("ToolUpdateRelease")
@@ -88,6 +89,7 @@
             Setup.Reset("ToolDownloadTranslate")
             Setup.Reset("ToolDownloadKeepModpack")
             Setup.Reset("ToolDownloadIgnoreQuilt")
+            Setup.Reset("ToolDownloadCert")
             Setup.Reset("ToolUpdateRelease")
             Setup.Reset("ToolUpdateSnapshot")
             Setup.Reset("ToolHelpChinese")
@@ -109,7 +111,7 @@
     End Sub
 
     '将控件改变路由到设置改变
-    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadKeepModpack.Change, CheckDownloadIgnoreQuilt.Change
+    Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadKeepModpack.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change
         If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked)
     End Sub
     Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderDebugAnim.Change, SliderDownloadThread.Change, SliderDownloadSpeed.Change

+ 33 - 8
Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb

@@ -1,4 +1,6 @@
-Public Class PageVersionMod
+Imports System.Security.Principal
+
+Public Class PageVersionMod
 
 #Region "初始化"
 
@@ -65,12 +67,12 @@
                 PanBack.Visibility = Visibility.Collapsed
                 Exit Sub
             End If
-            SearchBox.Text = ""
             '输出结果
             ModItems.Clear()
             For Each ModEntity As McMod In Mods
                 ModItems(ModEntity.RawFileName) = McModListItem(ModEntity)
             Next
+            SearchBox.Text = "" '这会触发结果刷新,所以需要在 ModItems 更新之后,详见 #3124 的视频
             RefreshResult(Mods)
         Catch ex As Exception
             Log(ex, "加载 Mod 列表 UI 失败", LogLevel.Feedback)
@@ -333,20 +335,33 @@
     End Sub
     Private Sub EDMods(ModList As IEnumerable(Of McMod), IsEnable As Boolean)
         Dim IsSuccessful As Boolean = True
-        For Each ModEntity In ModList.ToList
+        For Each ModE In ModList.ToList
+            Dim ModEntity = ModE '仅用于去除迭代变量无法修改的限制
             Dim NewPath As String = Nothing
             If ModEntity.State = McMod.McModState.Fine AndAlso Not IsEnable Then
                 '禁用
-                NewPath = ModEntity.Path & ".disabled"
+                NewPath = ModEntity.Path & If(File.Exists(ModEntity.Path & ".old"), ".old", ".disabled")
             ElseIf ModEntity.State = McMod.McModState.Disabled AndAlso IsEnable Then
                 '启用
-                NewPath = ModEntity.Path.Replace(".disabled", "").Replace(".old", "")
+                NewPath = ModEntity.RawPath
             Else
                 Continue For
             End If
             '重命名
             Try
-                If File.Exists(NewPath) AndAlso Not File.Exists(ModEntity.Path) Then Continue For '因为未知原因 Mod 的状态已经切换完了
+                If File.Exists(NewPath) Then
+                    If File.Exists(ModEntity.Path) Then
+                        '同时存在两个名称的 Mod
+                        If GetFileMD5(ModEntity.Path) <> GetFileMD5(NewPath) Then
+                            MyMsgBox($"目前同时存在启用和禁用的两个 Mod 文件:{vbCrLf} - {NewPath}{vbCrLf} - {ModEntity.Path}{vbCrLf}{vbCrLf}注意,这两个文件的内容并不相同。{vbCrLf}在手动删除或重命名其中一个文件后,才能继续操作。", "存在文件冲突")
+                            Continue For
+                        End If
+                    Else
+                        '已经重命名过了
+                        Log("[Mod] Mod 的状态已被切换", LogLevel.Debug)
+                        Continue For
+                    End If
+                End If
                 File.Delete(NewPath)
                 FileSystem.Rename(ModEntity.Path, NewPath)
             Catch ex As FileNotFoundException
@@ -389,6 +404,16 @@
         Try
             Dim IsSuccessful As Boolean = True
             Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
+            '确认需要删除的文件
+            ModList = ModList.SelectMany(
+            Function(Target As McMod)
+                If Target.State = McMod.McModState.Fine Then
+                    Return {Target.Path, Target.Path & If(File.Exists(Target.Path & ".old"), ".old", ".disabled")}
+                Else
+                    Return {Target.Path, Target.RawPath}
+                End If
+            End Function).Distinct.Where(Function(m) File.Exists(m)).Select(Function(m) New McMod(m)).ToList()
+            '实际删除文件
             For Each ModEntity In ModList
                 '删除
                 Try
@@ -410,8 +435,8 @@
                 '更改 Loader 和 UI 中的列表
                 McModLoader.Output.Remove(ModEntity)
                 ModItems.Remove(ModEntity.RawFileName)
-                Dim IndexOfUi As Integer = PanList.Children.IndexOf(PanList.Children.OfType(Of MyLocalModItem).First(Function(i) i.Entry Is ModEntity))
-                PanList.Children.RemoveAt(IndexOfUi)
+                Dim IndexOfUi As Integer = PanList.Children.IndexOf(PanList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry.Equals(ModEntity)))
+                If IndexOfUi >= 0 Then PanList.Children.RemoveAt(IndexOfUi)
             Next
             RefreshTitle()
             If Not IsSuccessful Then

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

@@ -26,7 +26,7 @@
                             <RowDefinition Height="28" />
                         </Grid.RowDefinitions>
                         <TextBlock Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏窗口标题" Margin="0,0,25,0" />
-                        <local:MyTextBox x:Name="TextArgumentTitle" Grid.Row="0" Grid.ColumnSpan="2" Tag="VersionArgumentTitle" Grid.Column="1" ToolTip="自定义游戏窗口的标题,若留空则使用全局设置的值。&#xa;支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {name}:游戏版本名&#xa; · {time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号" HintText="使用全局设置" MaxLength="100" />
+                        <local:MyTextBox x:Name="TextArgumentTitle" Grid.Row="0" Grid.ColumnSpan="2" Tag="VersionArgumentTitle" Grid.Column="1" ToolTip="自定义游戏窗口的标题,若留空则使用全局设置的值。&#xa;支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号" HintText="使用全局设置" MaxLength="100" />
                         <TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="自定义信息" Margin="0,0,25,0" />
                         <local:MyTextBox x:Name="TextArgumentInfo" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionArgumentInfo" Grid.Column="1" HintText="使用全局设置" ToolTip="该信息会显示在游戏主界面的左下角,与 F3 调试页面的左上角。&#xa;若留空,则使用全局设置的值。">
                             <local:MyTextBox.ValidateRules>
@@ -168,10 +168,8 @@
                     <Grid Height="35" Name="PanServerNide" Margin="0,12,0,0">
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
-                            <ColumnDefinition Width="Auto" SharedSizeGroup="Button" />
                         </Grid.ColumnDefinitions>
                         <local:MyButton x:Name="BtnServerNideWeb" Grid.Column="0" MinWidth="140" Text="服主后台" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" ColorType="Highlight" />
-                        <local:MyButton x:Name="BtnServerNideBbs" Grid.Column="1" MinWidth="140" Text="查看简介" Padding="13,0" Margin="0,0,20,0" HorizontalAlignment="Left" />
                     </Grid>
                     <local:MyButton x:Name="BtnServerAuthLittle" Height="35" MinWidth="150" Margin="0,12,0,0" Text="设置为 LittleSkin" HorizontalAlignment="Left" ColorType="Highlight" />
                 </StackPanel>
@@ -199,7 +197,7 @@
                     <local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如输入 --demo 则会以试玩模式启动游戏。&#xa;若留空,则使用全局设置的值。" HintText="使用全局设置" />
                     <TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,3" />
                     <local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionAdvanceRun" Grid.Column="1" HintText="" Margin="0,0,0,3"
-                                     ToolTip="该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行版本设置的命令。&#xa;&#xa;在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xa; · {path}:PCL 的 exe 文件所在的文件夹&#xa; · {minecraft}:.minecraft 文件夹&#xa; · {verpath}:版本文件夹(.minecraft\versions\版本名\)&#xa; · {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xa; · {java}:游戏运行时的 Java 文件夹&#xa;&#xa;除此之外,也支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {uuid}:玩家的 UUID&#xa; · {name}:游戏版本名&#xa; · {time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号&#xa;&#xa;例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}option.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。" />
+                                     ToolTip="该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行版本设置的命令。&#xa;&#xa;在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xa; · {path}:PCL 的 exe 文件所在的文件夹&#xa; · {minecraft}:.minecraft 文件夹&#xa; · {verpath}:版本文件夹(.minecraft\versions\版本名\)&#xa; · {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xa; · {java}:游戏运行时的 Java 文件夹&#xa;&#xa;除此之外,也支持以下替换标记:&#xa; · {user}:玩家名字&#xa; · {login}:玩家的登录方式&#xa; · {uuid}:玩家的 UUID&#xa; · {name}:游戏版本名&#xa; · {date}、{time}:当前的系统时间&#xa; · {version}:游戏对应的原版版本号&#xa;&#xa;例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}option.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
                     <local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="VersionAdvanceRunWait" Visibility="Collapsed"
                                       Text="等待命令执行完成后再继续启动" Margin="0,5,0,0" />
                     <StackPanel Margin="0,10,0,0" Orientation="Horizontal" Grid.Row="6" Grid.ColumnSpan="2">

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

@@ -121,9 +121,10 @@
     End Sub
     Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextServerEnter.ValidatedTextChanged, TextArgumentInfo.ValidatedTextChanged, TextAdvanceGame.ValidatedTextChanged, TextAdvanceJvm.ValidatedTextChanged, TextServerNide.ValidatedTextChanged, TextServerAuthName.ValidatedTextChanged, TextServerAuthRegister.ValidatedTextChanged, TextServerAuthServer.ValidatedTextChanged, TextArgumentTitle.ValidatedTextChanged, TextAdvanceRun.ValidatedTextChanged
         If AniControlEnabled = 0 Then
-            Dim HandledText As String = sender.Text
-            If sender.Tag = "VersionServerAuthServer" OrElse sender.Tag = "VersionServerAuthRegister" Then HandledText = HandledText.TrimEnd("/")
-            Setup.Set(sender.Tag, HandledText, Version:=PageVersionLeft.Version)
+            '#3194,不能删减 /
+            'Dim HandledText As String = sender.Text
+            'If sender.Tag = "VersionServerAuthServer" OrElse sender.Tag = "VersionServerAuthRegister" Then HandledText = HandledText.TrimEnd("/")
+            Setup.Set(sender.Tag, sender.Text, Version:=PageVersionLeft.Version)
         End If
     End Sub
     Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderRamCustom.Change
@@ -398,14 +399,12 @@ PreFin:
     Private Sub BtnServerNideWeb_Click(sender As Object, e As EventArgs) Handles BtnServerNideWeb.Click
         OpenWebsite("https://login.mc-user.com:233/server/intro")
     End Sub
-    Private Sub BtnServerNideBbs_Click(sender As Object, e As EventArgs) Handles BtnServerNideBbs.Click
-        OpenWebsite("https://www.mcbbs.net/thread-729821-1-1.html")
-    End Sub
 
     'LittleSkin
     Private Sub BtnServerAuthLittle_Click(sender As Object, e As EventArgs) Handles BtnServerAuthLittle.Click
         If TextServerAuthServer.Text <> "" AndAlso TextServerAuthServer.Text <> "https://littleskin.cn/api/yggdrasil" AndAlso
-            MyMsgBox("即将把第三方登录设置覆盖为自动使用 LittleSkin 登录,是否继续?", "覆盖确认", "确定", "取消") = 2 Then Exit Sub
+            MyMsgBox("即将把第三方登录设置覆盖为 LittleSkin 登录。" & vbCrLf & "除非你是服主,或者服主要求你这样做,否则请不要继续。" & vbCrLf & vbCrLf & "是否确实需要覆盖当前设置?",
+                     "设置覆盖确认", "继续", "取消") = 2 Then Exit Sub
         TextServerAuthServer.Text = "https://littleskin.cn/api/yggdrasil"
         TextServerAuthRegister.Text = "https://littleskin.cn/auth/register"
         TextServerAuthName.Text = "LittleSkin 登录"

+ 0 - 3
Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj

@@ -838,9 +838,6 @@
   <ItemGroup>
     <Resource Include="Images\Blocks\Fabric.png" />
   </ItemGroup>
-  <ItemGroup>
-    <Resource Include="Images\Heads\MCBBS.png" />
-  </ItemGroup>
   <ItemGroup>
     <None Include="Resources\forge-installer.jar" />
   </ItemGroup>

+ 5 - 5
Plain Craft Launcher 2/Resources/Custom.xaml

@@ -30,7 +30,7 @@
                     Text="任意项目都可以添加 Width 与 Height 属性来控制宽高。HorizontalAlignment 属性可以控制对齐:Center 代表居中,Right 代表居右,例如这段文本就被居右了。" />
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,6"
                     Text="Image 代表图片,你需要在它的 Source 属性中填写一个网址或文件路径,它会从该处获取图片并显示。通常需要使用 Height 限制它的高度。" />
-		<Image Height="50" HorizontalAlignment="Center" Source="https://www.mcbbs.net/template/mcbbs/image/logo_sc.png" />
+		<Image Height="50" HorizontalAlignment="Center" Source="https://zh.minecraft.wiki/images/Grass_Block_JE7_BE6.png?bf67e" />
 	</StackPanel>
 </local:MyCard>
 
@@ -58,7 +58,7 @@
         <TextBlock TextWrapping="Wrap" Margin="0,0,0,10"
                     Text="将按钮的 EventType 属性设为 打开网页,然后在 EventData 属性中写入网址,即可通过点击按钮打开网页。" />
 		<local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
-                    Text="打开 MCBBS" EventType="打开网页" EventData="https://www.mcbbs.net/" />
+                    Text="打开 Minecraft Wiki" EventType="打开网页" EventData="https://zh.minecraft.wiki/" />
         <TextBlock TextWrapping="Wrap" Margin="0,10,0,10"
                     Text="或者将 EventType 属性改为 弹出窗口,然后在 EventData 属性中写入弹窗的标题与内容……" />
 		<local:MyButton Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
@@ -119,7 +119,7 @@
 		<TextBlock Margin="0,0,0,4"
                     Text="· 下载文件:在点击时要求选择文件保存位置,并且下载 EventData 的链接中的文件" />
 		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
-                    Text="下载 MCBBS 的 Logo" EventType="下载文件" EventData="https://www.mcbbs.net/template/mcbbs/image/logo_sc.png" />
+                    Text="下载百度的 Logo" EventType="下载文件" EventData="https://www.baidu.com/img/flexible/logo/pc/result.png" />
 		<TextBlock Margin="0,4,0,4"
                     Text="· 打开帮助:跳转到 EventData 对应的帮助文件(输入相对 Help 文件夹的路径)"/>
 		<local:MyButton Margin="0,5,0,6" Width="250" Height="35"
@@ -153,7 +153,7 @@
 			<local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
                         Text="内存优化" EventType="内存优化" />
 			<local:MyButton Margin="0,0,10,0" Width="140" Height="35" HorizontalAlignment="Left" Padding="13,0,13,0"
-                        Text="下载作者的头像" EventType="下载文件" EventData="https://www.mcbbs.net/uc_server/data/avatar/000/14/51/06_avatar_small.jpg?ts=1471402592" />
+                        Text="下载百度的 Logo" EventType="下载文件" EventData="https://www.baidu.com/img/flexible/logo/pc/result.png" />
 		</StackPanel>
 	</StackPanel>
 </local:MyCard>
@@ -227,7 +227,7 @@
             <TextBlock TextWrapping="Wrap" Text=" 浓度 7 " Margin="5,0" Background="{DynamicResource ColorBrush7}" />
             <TextBlock TextWrapping="Wrap" Text=" 浓度 8 " Margin="5,0" Background="{DynamicResource ColorBrush8}" />
 		</StackPanel>
-		<TextBlock Margin="0,15,0,8" Foreground="{DynamicResource ColorBrush2}" Background="{DynamicResource ColorBrush6}"
+		<TextBlock TextWrapping="Wrap" Margin="0,15,0,8" Foreground="{DynamicResource ColorBrush2}" Background="{DynamicResource ColorBrush6}"
                     Text="除了字体颜色,你也可以将主题色用于背景色、边框颜色等颜色参数。例如这行字的颜色为浓度 2,背景为浓度 6。" />
 	</StackPanel>
 </local:MyCard>

BIN
Plain Craft Launcher 2/Resources/Help.zip


Fișier diff suprimat deoarece este prea mare
+ 243 - 243
Plain Craft Launcher 2/Resources/ModData.txt


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


Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff