From f78250020ef8c278d77c4b17cb643eccfd6aa771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E8=85=BE=E7=8C=AB=E8=B7=83?= <1043137532@qq.com> Date: Sat, 2 Mar 2024 17:53:55 +0800 Subject: [PATCH] 2.6.15 --- Plain Craft Launcher 2/FormMain.xaml.vb | 16 + Plain Craft Launcher 2/Images/Heads/MCBBS.png | Bin 0 -> 715 bytes .../Modules/Base/ModBase.vb | 8 +- Plain Craft Launcher 2/Modules/Base/ModNet.vb | 213 +++++----- .../Modules/Minecraft/ModComp.vb | 2 +- .../Modules/Minecraft/ModDownload.vb | 4 +- .../Modules/Minecraft/ModModpack.vb | 2 +- .../Modules/Minecraft/MyCompItem.xaml.vb | 2 +- .../Modules/Minecraft/MyLocalModItem.xaml.vb | 2 +- .../My Project/AssemblyInfo.vb | 4 +- .../Pages/PageDownload/ModDownloadLib.vb | 371 +++++++++--------- .../Plain Craft Launcher 2.vbproj | 3 + Plain Craft Launcher 2/Resources/Help.zip | Bin 75015 -> 75009 bytes .../Resources/forge-installer.jar | Bin 3163 -> 3222 bytes 14 files changed, 329 insertions(+), 298 deletions(-) create mode 100644 Plain Craft Launcher 2/Images/Heads/MCBBS.png diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index 8089006..9d4d744 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -10,6 +10,17 @@ Public Class FormMain Dim FeatureList As New List(Of KeyValuePair(Of Integer, String)) '统计更新日志条目 #If BETA Then + If LastVersion < 317 Then 'Release 2.6.15 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法安装 Forge 的 Bug")) + FeatureCount += 2 + BugCount += 2 + End If + If LastVersion < 315 Then 'Release 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 < 313 Then 'Release 2.6.13 FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动 Forge 1.18.3+ 的 Bug")) FeatureCount += 6 @@ -129,6 +140,11 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- + If LastVersion < 316 Then 'Snapshot 2.6.15 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法安装 Forge 的 Bug")) + FeatureCount += 2 + BugCount += 2 + End If 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 关站,移除其相关内容")) diff --git a/Plain Craft Launcher 2/Images/Heads/MCBBS.png b/Plain Craft Launcher 2/Images/Heads/MCBBS.png new file mode 100644 index 0000000000000000000000000000000000000000..b76dd5afda7421c23141d1053d934a09ba362886 GIT binary patch literal 715 zcmV;+0yO=JP)u<0$52z zK~zY`&6P2aoG=iDFBNTu$WdC1xIP&12SD`p9`^$RLPCt z*GwpV%LDrhh`cJLuRV~zURE_+<>353kFnkj#wLO)!V2PRZY|C+?O6=eK$Qd?>s8{BlAAIEF=Eokghtpxc;8v&m+Xd{_*~ zme0}e4*@>zZ!bUCHPF}JL#e+0l;{>2oNhK52Bq+qGa$Xbc7V!D`lHyE=#*c%JBus8 z??)037`d6lumA$1c6tDO%x4EK;X{OHL8(;T3^*Ob%$6@IB1-3!OUM=tT`q_tJ*qSW zc?!6WTLfVLMQJc>MB0J^?P*~0@Sqgz+L>ji^q4c)%ZZ7*koeD_JedFh literal 0 HcmV?d00001 diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index b6d5130..f97ba7e 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -11,12 +11,12 @@ Public Module ModBase #Region "声明" '下列版本信息由更新器自动修改 - Public Const VersionBaseName As String = "2.6.14" '不含分支前缀的显示用版本名 - Public Const VersionStandardCode As String = "2.6.14." & VersionBranchCode '标准格式的四段式版本号 + Public Const VersionBaseName As String = "2.6.15" '不含分支前缀的显示用版本名 + Public Const VersionStandardCode As String = "2.6.15." & VersionBranchCode '标准格式的四段式版本号 #If BETA Then - Public Const VersionCode As Integer = 313 'Release + Public Const VersionCode As Integer = 317 'Release #Else - Public Const VersionCode As Integer = 314 'Snapshot + Public Const VersionCode As Integer = 316 'Snapshot #End If '自动生成的版本信息 Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb index 82673de..8084128 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb @@ -28,7 +28,7 @@ Public Module ModNet ''' ''' 网页的 Url。 ''' 网页的编码,通常为 UTF-8。 - Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Optional Accept As String = "application/json, text/javascript, */*; q=0.01") As String + Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Optional Accept As String = "application/json, text/javascript, */*; q=0.01", Optional UseBrowserUserAgent As Boolean = False) As String Dim RetryCount As Integer = 0 Dim RetryException As Exception = Nothing Dim StartTime As Long = GetTimeTick() @@ -36,15 +36,15 @@ Public Module ModNet Retry: Select Case RetryCount Case 0 '正常尝试 - Return NetGetCodeByClient(Url, Encoding, 10000, Accept) + Return NetGetCodeByClient(Url, Encoding, 10000, Accept, UseBrowserUserAgent) Case 1 '慢速重试 Thread.Sleep(500) - Return NetGetCodeByClient(Url, Encoding, 30000, Accept) + Return NetGetCodeByClient(Url, Encoding, 30000, Accept, UseBrowserUserAgent) Case Else '快速重试 If GetTimeTick() - StartTime > 5500 Then '若前两次加载耗费 5 秒以上,才进行重试 Thread.Sleep(500) - Return NetGetCodeByClient(Url, Encoding, 4000, Accept) + Return NetGetCodeByClient(Url, Encoding, 4000, Accept, UseBrowserUserAgent) Else Throw RetryException End If @@ -63,7 +63,7 @@ Retry: End Select End Try End Function - Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Timeout As Integer, Accept As String) As String + Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Timeout As Integer, Accept As String, Optional UseBrowserUserAgent As Boolean = False) As String Url = SecretCdnSign(Url) Log("[Net] 获取客户端网络结果:" & Url & ",最大超时 " & Timeout) Dim Request As CookieWebClient @@ -77,7 +77,7 @@ Retry: Request.Headers("Accept") = Accept Request.Headers("Accept-Language") = "en-US,en;q=0.5" Request.Headers("X-Requested-With") = "XMLHttpRequest" - SecretHeadersSign(Url, Request) + SecretHeadersSign(Url, Request, UseBrowserUserAgent) Return Request.DownloadString(Url) Catch ex As Exception If ex.GetType.Equals(GetType(WebException)) AndAlso CType(ex, WebException).Status = WebExceptionStatus.Timeout Then @@ -98,7 +98,7 @@ Retry: ''' 网页的编码,通常为 UTF-8。 ''' 如果第一次尝试失败,换用的备用 URL。 Public Function NetGetCodeByRequestRetry(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", - Optional IsJson As Boolean = False, Optional BackupUrl As String = Nothing) + Optional IsJson As Boolean = False, Optional BackupUrl As String = Nothing, Optional UseBrowserUserAgent As Boolean = False) Dim RetryCount As Integer = 0 Dim RetryException As Exception = Nothing Dim StartTime As Long = GetTimeTick() @@ -106,15 +106,15 @@ Retry: Retry: Select Case RetryCount Case 0 '正常尝试 - Return NetGetCodeByRequestOnce(Url, Encode, 10000, IsJson, Accept) + Return NetGetCodeByRequestOnce(Url, Encode, 10000, IsJson, Accept, UseBrowserUserAgent) Case 1 '慢速重试 Thread.Sleep(500) - Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 30000, IsJson, Accept) + Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 30000, IsJson, Accept, UseBrowserUserAgent) Case Else '快速重试 If GetTimeTick() - StartTime > 5500 Then '若前两次加载耗费 5 秒以上,才进行重试 Thread.Sleep(500) - Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 4000, IsJson, Accept) + Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 4000, IsJson, Accept, UseBrowserUserAgent) Else Throw RetryException End If @@ -140,7 +140,7 @@ Retry: ''' ''' 网页的 Url。 ''' 网页的编码,通常为 UTF-8。 - Public Function NetGetCodeByRequestMuity(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False) + Public Function NetGetCodeByRequestMulty(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False) Dim Threads As New List(Of Thread) Dim RequestResult = Nothing Dim RequestEx As Exception = Nothing @@ -182,7 +182,7 @@ RequestFinished: Loop Throw New Exception("未知错误") End Function - Public Function NetGetCodeByRequestOnce(Url As String, Optional Encode As Encoding = Nothing, Optional Timeout As Integer = 30000, Optional IsJson As Boolean = False, Optional Accept As String = "") + Public Function NetGetCodeByRequestOnce(Url As String, Optional Encode As Encoding = Nothing, Optional Timeout As Integer = 30000, Optional IsJson As Boolean = False, Optional Accept As String = "", Optional UseBrowserUserAgent As Boolean = False) If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求") Url = SecretCdnSign(Url) Log($"[Net] 获取网络结果:{Url},超时 {Timeout}ms{If(IsJson, ",要求 json", "")}") @@ -192,7 +192,7 @@ RequestFinished: If Url.StartsWith("https", StringComparison.OrdinalIgnoreCase) Then Request.ProtocolVersion = HttpVersion.Version11 Request.Timeout = Timeout Request.Accept = Accept - SecretHeadersSign(Url, Request) + SecretHeadersSign(Url, Request, UseBrowserUserAgent) Using res As HttpWebResponse = Request.GetResponse() Using HttpStream As Stream = res.GetResponseStream() HttpStream.ReadTimeout = Timeout @@ -220,9 +220,9 @@ RequestFinished: ''' 以多线程下载网页文件的方式获取网页源代码。 ''' ''' 网页的 Url。 - Public Function NetGetCodeByDownload(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False) As String + Public Function NetGetCodeByDownload(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String Dim Temp As String = PathTemp & "Cache\Code\" & Url.GetHashCode() & "_" & GetUuid() - Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, Temp, New FileChecker With {.IsJson = IsJson})}) + Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)}) Try NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(" & Url & ")") NetGetCodeByDownload = ReadFile(Temp) @@ -235,9 +235,9 @@ RequestFinished: ''' 以多线程下载网页文件的方式获取网页源代码。 ''' ''' 网页的 Url 列表。 - Public Function NetGetCodeByDownload(Urls As String(), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False) As String + Public Function NetGetCodeByDownload(Urls As String(), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String Dim Temp As String = PathTemp & "Cache\Code\" & Urls(0).GetHashCode() & "_" & GetUuid() - Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson})}) + Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)}) Try NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(第一下载源:" & Urls(0) & ")") NetGetCodeByDownload = ReadFile(Temp) @@ -252,7 +252,7 @@ RequestFinished: ''' ''' 网络 Url。 ''' 下载的本地地址。 - Public Sub NetDownload(Url As String, LocalFile As String) + Public Sub NetDownload(Url As String, LocalFile As String, Optional UseBrowserUserAgent As Boolean = False) Log("[Net] 直接下载文件:" & Url) '初始化 @@ -268,7 +268,11 @@ RequestFinished: '下载 Using Client As New WebClient Try - Client.Headers(HttpRequestHeader.UserAgent) = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36" + If UseBrowserUserAgent Then + Client.Headers(HttpRequestHeader.UserAgent) = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36" + Else + Client.Headers(HttpRequestHeader.UserAgent) = "PCL2/" & VersionStandardCode + End If Client.Headers(HttpRequestHeader.Referer) = "http://" & VersionCode & ".pcl2.server/" Client.DownloadFile(Url, LocalFile) Catch ex As Exception @@ -369,7 +373,7 @@ RequestFinished: ''' ''' 发送一次网络请求并获取返回内容。 ''' - Public Function NetRequestOnce(Url As String, Method As String, Data As Object, ContentType As String, Optional Timeout As Integer = 25000, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True) As String + Public Function NetRequestOnce(Url As String, Method As String, Data As Object, ContentType As String, Optional Timeout As Integer = 25000, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True, Optional UseBrowserUserAgent As Boolean = False) As String If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求") Url = SecretCdnSign(Url) If MakeLog Then Log("[Net] 发起网络请求(" & Method & "," & Url & "),最大超时 " & Timeout) @@ -392,7 +396,7 @@ RequestFinished: End If Req.ContentType = ContentType Req.Timeout = Timeout - SecretHeadersSign(Url, Req) + SecretHeadersSign(Url, Req, UseBrowserUserAgent) If Url.StartsWith("https", StringComparison.OrdinalIgnoreCase) Then Req.ProtocolVersion = HttpVersion.Version11 If Method = "POST" OrElse Method = "PUT" Then Req.ContentLength = SendData.Length @@ -790,6 +794,10 @@ RequestFinished: ''' 文件的校验规则。 ''' Public Check As FileChecker + ''' + ''' 下载时是否添加浏览器 UA。 + ''' + Public UseBrowserUserAgent As Boolean ''' ''' 上次记速时的时间。 @@ -889,7 +897,7 @@ RequestFinished: ''' 新建一个需要下载的文件。 ''' ''' 包含文件名的本地地址。 - Public Sub New(Urls As String(), LocalPath As String, Optional Check As FileChecker = Nothing) + Public Sub New(Urls As String(), LocalPath As String, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) Dim Sources As New List(Of NetSource) Dim Count As Integer = 0 Urls = Urls.Distinct.ToArray @@ -900,6 +908,7 @@ RequestFinished: Me.Sources = Sources.ToArray Me.LocalPath = LocalPath Me.Check = Check + Me.UseBrowserUserAgent = UseBrowserUserAgent Me.LocalName = GetFileNameFromPath(LocalPath) End Sub @@ -1030,7 +1039,7 @@ StartThread: 'HttpRequest.Proxy = Nothing 'new WebProxy(Ip, Port) HttpRequest.Timeout = Timeout HttpRequest.AddRange(Info.DownloadStart) - SecretHeadersSign(Info.Source.Url, HttpRequest) + SecretHeadersSign(Info.Source.Url, HttpRequest, UseBrowserUserAgent) Using HttpResponse As HttpWebResponse = HttpRequest.GetResponse() '文件大小校验 Dim ThreadFileSize = HttpResponse.ContentLength @@ -1909,84 +1918,86 @@ Retry: Log(ex, "下载管理启动线程 1 出错", LogLevel.Assert) End Try End Sub, "NetManager ThreadStarter Single") - RunInNewThread(Sub() - Try - Dim LastLoopTime As Long - While True - LastLoopTime = GetTimeTick() - '开启新线程 - 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 = 1 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, "下载管理启动线程 2 出错", LogLevel.Assert) - End Try - End Sub, "NetManager ThreadStarter Odd") - RunInNewThread(Sub() - Try - Dim LastLoopTime As Long - NetTaskSpeedLimitLeftLast = GetTimeTick() - While True - Dim TimeNow = GetTimeTick() - LastLoopTime = TimeNow - '增加限速余量 - If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft = NetTaskSpeedLimitHigh / 1000 * (TimeNow - NetTaskSpeedLimitLeftLast) - NetTaskSpeedLimitLeftLast = TimeNow - '刷新公开属性 - RefreshStat() - '等待直至 170 ms - Do While GetTimeTick() - LastLoopTime < 170 - Thread.Sleep(10) - Loop - End While - Catch ex As Exception - Log(ex, "下载管理刷新线程出错", LogLevel.Assert) - End Try - End Sub, "NetManager StatRefresher") + RunInNewThread( + Sub() + Try + Dim LastLoopTime As Long + While True + LastLoopTime = GetTimeTick() + '开启新线程 + 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 = 1 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, "下载管理启动线程 2 出错", LogLevel.Assert) + End Try + End Sub, "NetManager ThreadStarter Odd") + RunInNewThread( + Sub() + Try + Dim LastLoopTime As Long + NetTaskSpeedLimitLeftLast = GetTimeTick() + While True + Dim TimeNow = GetTimeTick() + LastLoopTime = TimeNow + '增加限速余量 + If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft = NetTaskSpeedLimitHigh / 1000 * (TimeNow - NetTaskSpeedLimitLeftLast) + NetTaskSpeedLimitLeftLast = TimeNow + '刷新公开属性 + RefreshStat() + '等待直至 170 ms + Do While GetTimeTick() - LastLoopTime < 170 + Thread.Sleep(10) + Loop + End While + Catch ex As Exception + Log(ex, "下载管理刷新线程出错", LogLevel.Assert) + End Try + End Sub, "NetManager StatRefresher") End Sub Private IsManagerStarted As Boolean = False diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb index e443eba..72fc6a7 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb @@ -1196,7 +1196,7 @@ Retry: ''' ''' 目标本地文件夹,或完整的文件路径。会自动判断类型。 Public Function ToNetFile(LocalAddress As String) As NetFile - Return New NetFile(DownloadAddress, LocalAddress & If(LocalAddress.EndsWith("\"), FileName, "")) + Return New NetFile(DownloadAddress, LocalAddress & If(LocalAddress.EndsWith("\"), FileName, ""), UseBrowserUserAgent:=True) End Function '实例化 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb index d9c8eb5..e41a940 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb @@ -452,7 +452,7 @@ ''' Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain) Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult)) - Dim Result As String = NetGetCodeByRequestRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html") + Dim Result As String = NetGetCodeByRequestRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html", UseBrowserUserAgent:=True) If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") '获取所有版本信息 Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)") @@ -589,7 +589,7 @@ Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry))) Dim Result As String Try - Result = NetGetCodeByDownload("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input & ".html") + Result = NetGetCodeByDownload("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input & ".html", UseBrowserUserAgent:=True) Catch ex As Exception If GetExceptionSummary(ex).Contains("(404)") Then Throw New Exception("没有可用版本") diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb index 58a3bd2..745a6ed 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb @@ -439,7 +439,7 @@ Retry: '添加下载文件 FileList.Add(New NetFile(File("downloads").Select(Function(t) t.ToString.Replace("://edge.forgecdn", "://media.forgecdn")).ToArray, '修复 #2390 PathMcFolder & "versions\" & VersionName & "\" & File("path").ToString, - New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString))) + New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString), True)) Next If FileList.Count > 0 Then InstallLoaders.Add(New LoaderDownload("下载额外文件", FileList) With {.ProgressWeight = FileList.Count * 1.5}) '每个 Mod 需要 1.5s diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb b/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb index 9462e98..9acefae 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb @@ -57,7 +57,7 @@ RetryStart: Url = Url.Replace("/256/256/", "/64/64/") '#3075:部分 Mod 不存在 64x64 图标,所以重试时不再缩小 End If '下载图片 - NetDownload(Url, LocalFileAddress & DownloadEnd) + NetDownload(Url, LocalFileAddress & DownloadEnd, True) Dim LoadError As Exception = Nothing RunInUiWait( Sub() diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb index 3483e0c..1fa401f 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb @@ -59,7 +59,7 @@ RetryStart: Url = Url.Replace("/256/256/", "/64/64/") '#3075:部分 Mod 不存在 64x64 图标,所以重试时不再缩小 End If '下载图片 - NetDownload(Url, LocalFileAddress & DownloadEnd) + NetDownload(Url, LocalFileAddress & DownloadEnd, True) Dim LoadError As Exception = Nothing RunInUiWait( Sub() diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index d402fe4..bbd6bbb 100644 --- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb +++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb @@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 ' 方法是按如下所示使用“*” - - + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index 1eaffa1..938b2f2 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -526,7 +526,7 @@ Public Module ModDownloadLib '官方源 Dim PageData As String Try - PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html") + PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True) Task.Progress = 0.8 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0)) Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources(0)) @@ -691,7 +691,7 @@ Retry: '官方源 Dim PageData As String Try - PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html") + PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True) Task.Progress = 0.8 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0)) Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources(0)) @@ -1317,206 +1317,207 @@ Retry: If Version.Split(".")(0) >= 20 Then Log("[Download] 检测为新版 Forge:" & Version) Dim Libs As List(Of McLibToken) = Nothing - Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 Mod 加载器支持库文件", Sub(Task As LoaderTask(Of String, List(Of NetFile))) - Task.Output = New List(Of NetFile) - Dim Installer As ZipArchive = Nothing - Try - '解压并获取、合并两个 Json 的信息 - Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open)) - Task.Progress = 0.2 - Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open)) - Dim Json2 As JObject = GetJson(ReadFile(Installer.GetEntry("version.json").Open)) - Json.Merge(Json2) - '获取 Lib 下载信息 - Libs = McLibListGetWithJson(Json, True) - '添加 Mappings 下载信息 - If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then - '下载原版 Json 文件 - Task.Progress = 0.4 - Dim RawJson As JObject = GetJson(NetGetCodeByDownload(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True)) - '[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg] - Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(0) - Dim Address = McLibGet(OriginalName).Replace(".jar", "-mappings." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1)) - Libs.Add(New McLibToken With {.IsJumpLoader = False, .IsNatives = False, .LocalPath = Address, .OriginalName = OriginalName, + Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 Mod 加载器支持库文件", + Sub(Task As LoaderTask(Of String, List(Of NetFile))) + Task.Output = New List(Of NetFile) + Dim Installer As ZipArchive = Nothing + Try + '解压并获取、合并两个 Json 的信息 + Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open)) + Task.Progress = 0.2 + Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open)) + Dim Json2 As JObject = GetJson(ReadFile(Installer.GetEntry("version.json").Open)) + Json.Merge(Json2) + '获取 Lib 下载信息 + Libs = McLibListGetWithJson(Json, True) + '添加 Mappings 下载信息 + If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then + '下载原版 Json 文件 + Task.Progress = 0.4 + Dim RawJson As JObject = GetJson(NetGetCodeByDownload(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True)) + '[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg] + Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(0) + Dim Address = McLibGet(OriginalName).Replace(".jar", "-mappings." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1)) + Libs.Add(New McLibToken With {.IsJumpLoader = False, .IsNatives = False, .LocalPath = Address, .OriginalName = OriginalName, .Url = RawJson("downloads")("client_mappings")("url"), .Size = RawJson("downloads")("client_mappings")("size"), .SHA1 = RawJson("downloads")("client_mappings")("sha1")}) - Log("[Download] 需要下载 Mappings:" & Address) - End If - Task.Progress = 0.8 - '去除其中的原始 Forge 项 - For i = 0 To Libs.Count - 1 - If Libs(i).LocalPath.EndsWith("forge-" & Inherit & "-" & Version & ".jar") OrElse + Log("[Download] 需要下载 Mappings:" & Address) + End If + Task.Progress = 0.8 + '去除其中的原始 Forge 项 + For i = 0 To Libs.Count - 1 + If Libs(i).LocalPath.EndsWith("forge-" & Inherit & "-" & Version & ".jar") OrElse Libs(i).LocalPath.EndsWith("forge-" & Inherit & "-" & Version & "-client.jar") Then - Log("[Download] 已从待下载 Forge 支持库中移除:" & Libs(i).LocalPath, LogLevel.Debug) - Libs.RemoveAt(i) - Exit For - End If - Next - Task.Output = McLibFixFromLibToken(Libs, PathMcFolder) - Catch ex As Exception - Throw New Exception("获取新版 Forge 支持库列表失败", ex) - Finally - '释放文件 - If Installer IsNot Nothing Then Installer.Dispose() - End Try - End Sub) With {.ProgressWeight = 2}) + Log("[Download] 已从待下载 Forge 支持库中移除:" & Libs(i).LocalPath, LogLevel.Debug) + Libs.RemoveAt(i) + Exit For + End If + Next + Task.Output = McLibFixFromLibToken(Libs, PathMcFolder) + Catch ex As Exception + Throw New Exception("获取新版 Forge 支持库列表失败", ex) + Finally + '释放文件 + If Installer IsNot Nothing Then Installer.Dispose() + End Try + End Sub) With {.ProgressWeight = 2}) Loaders.Add(New LoaderDownload("下载 Mod 加载器支持库文件", New List(Of NetFile)) With {.ProgressWeight = 12}) Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("获取 Mod 下载器支持库文件", - Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) + Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) #Region "Forge 文件" - If IsCustomFolder Then - For Each LibFile As McLibToken In Libs - Dim RealPath As String = LibFile.LocalPath.Replace(PathMcFolder, McFolder) - If Not File.Exists(RealPath) Then - Directory.CreateDirectory(IO.Path.GetDirectoryName(RealPath)) - CopyFile(LibFile.LocalPath, RealPath) - End If - If ModeDebug Then Log("[Download] 复制的 Forge 支持库文件:" & LibFile.LocalPath) - Next - End If + If IsCustomFolder Then + For Each LibFile As McLibToken In Libs + Dim RealPath As String = LibFile.LocalPath.Replace(PathMcFolder, McFolder) + If Not File.Exists(RealPath) Then + Directory.CreateDirectory(IO.Path.GetDirectoryName(RealPath)) + CopyFile(LibFile.LocalPath, RealPath) + End If + If ModeDebug Then Log("[Download] 复制的 Forge 支持库文件:" & LibFile.LocalPath) + Next + End If #End Region #Region "原版文件" - '等待原版文件下载完成 - If ClientDownloadLoader Is Nothing Then Exit Sub - Dim TargetLoaders As List(Of LoaderBase) = + '等待原版文件下载完成 + If ClientDownloadLoader Is Nothing Then Exit Sub + Dim TargetLoaders As List(Of LoaderBase) = ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName). Where(Function(l) l.State <> LoadState.Finished).ToList - If TargetLoaders.Count > 0 Then Log("[Download] Forge 安装正在等待原版文件下载完成") - Do While TargetLoaders.Count > 0 AndAlso Not Task.IsAborted - TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList - Thread.Sleep(50) - Loop - If Task.IsAborted Then Exit Sub - '拷贝原版文件 - If Not IsCustomFolder Then Exit Sub - SyncLock VanillaSyncLock - Dim ClientName As String = GetFolderNameFromPath(ClientFolder) - Directory.CreateDirectory(McFolder & "versions\" & Inherit) - If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".json") Then - CopyFile(ClientFolder & ClientName & ".json", McFolder & "versions\" & Inherit & "\" & Inherit & ".json") - End If - If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") Then - CopyFile(ClientFolder & ClientName & ".jar", McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") - End If - End SyncLock + If TargetLoaders.Count > 0 Then Log("[Download] Forge 安装正在等待原版文件下载完成") + Do While TargetLoaders.Count > 0 AndAlso Not Task.IsAborted + TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList + Thread.Sleep(50) + Loop + If Task.IsAborted Then Exit Sub + '拷贝原版文件 + If Not IsCustomFolder Then Exit Sub + SyncLock VanillaSyncLock + Dim ClientName As String = GetFolderNameFromPath(ClientFolder) + Directory.CreateDirectory(McFolder & "versions\" & Inherit) + If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".json") Then + CopyFile(ClientFolder & ClientName & ".json", McFolder & "versions\" & Inherit & "\" & Inherit & ".json") + End If + If Not File.Exists(McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") Then + CopyFile(ClientFolder & ClientName & ".jar", McFolder & "versions\" & Inherit & "\" & Inherit & ".jar") + End If + End SyncLock #End Region - End Sub) With {.ProgressWeight = 0.1, .Show = False}) + End Sub) With {.ProgressWeight = 0.1, .Show = False}) Loaders.Add(New LoaderTask(Of Boolean, Boolean)("安装 Mod 加载器(方式 A)", - Sub(Task As LoaderTask(Of Boolean, Boolean)) - Dim Installer As ZipArchive = Nothing - Try - Log("[Download] 开始进行新版方式 Forge 安装:" & InstallerAddress) - '记录当前文件夹列表(在新建目标文件夹之前) - Dim OldList = New DirectoryInfo(McFolder & "versions\").EnumerateDirectories. + Sub(Task As LoaderTask(Of Boolean, Boolean)) + Dim Installer As ZipArchive = Nothing + Try + Log("[Download] 开始进行新版方式 Forge 安装:" & InstallerAddress) + '记录当前文件夹列表(在新建目标文件夹之前) + Dim OldList = New DirectoryInfo(McFolder & "versions\").EnumerateDirectories. Select(Function(i) i.FullName).ToList() - '解压并获取信息 - Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open)) - Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open)) - '新建目标版本文件夹 - Directory.CreateDirectory(VersionFolder) - Task.Progress = 0.04 - '释放 launcher_installer.json - McFolderLauncherProfilesJsonCreate(McFolder) - Task.Progress = 0.05 - '运行 Forge 安装器 - Dim UseJavaWrapper As Boolean = True + '解压并获取信息 + Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open)) + Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open)) + '新建目标版本文件夹 + Directory.CreateDirectory(VersionFolder) + Task.Progress = 0.04 + '释放 launcher_installer.json + McFolderLauncherProfilesJsonCreate(McFolder) + Task.Progress = 0.05 + '运行 Forge 安装器 + Dim UseJavaWrapper As Boolean = True Retry: - Try - '释放 Forge 注入器 - WriteFile(PathTemp & "Cache\forge_installer.jar", GetResources("ForgeInstaller")) - Task.Progress = 0.06 - '运行注入器 - ForgeInjector(InstallerAddress, Task, McFolder, UseJavaWrapper) - Task.Progress = 0.97 - Catch ex As Exception - If UseJavaWrapper Then - Log(ex, "使用 JavaWrapper 安装 Forge 失败,将不使用 JavaWrapper 并重试") - UseJavaWrapper = False - GoTo Retry - Else - Throw New Exception("运行 Forge 安装器失败", ex) - End If - End Try - '拷贝新增的版本 Json - Dim DeltaList = New DirectoryInfo(McFolder & "versions\").EnumerateDirectories. + Try + '释放 Forge 注入器 + WriteFile(PathTemp & "Cache\forge_installer.jar", GetResources("ForgeInstaller")) + Task.Progress = 0.06 + '运行注入器 + ForgeInjector(InstallerAddress, Task, McFolder, UseJavaWrapper) + Task.Progress = 0.97 + Catch ex As Exception + If UseJavaWrapper Then + Log(ex, "使用 JavaWrapper 安装 Forge 失败,将不使用 JavaWrapper 并重试") + UseJavaWrapper = False + GoTo Retry + Else + Throw New Exception("运行 Forge 安装器失败", ex) + End If + End Try + '拷贝新增的版本 Json + Dim DeltaList = New DirectoryInfo(McFolder & "versions\").EnumerateDirectories. SkipWhile(Function(i) OldList.Contains(i.FullName)).ToList() - If DeltaList.Count > 1 Then - '它可能和 OptiFine 安装同时运行,导致增加的文件不止一个(这导致了 #151) - '也可能是因为 Forge 安装器的 Bug,生成了一个名字错误的文件夹,所以需要检查文件夹是否为空 - DeltaList = DeltaList.Where(Function(l) l.Name.Contains("forge") AndAlso l.EnumerateFiles.Any).ToList - End If - If DeltaList.Count = 1 Then '如果没有新增文件夹,那么预测的文件夹名就是正确的 - Dim JsonFile As FileInfo = DeltaList(0).EnumerateFiles.First() - WriteFile(VersionFolder & Id & ".json", ReadFile(JsonFile.FullName)) - End If - '新建 mods 文件夹 - Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\") - Catch ex As Exception - Throw New Exception("安装新 Forge 版本失败", ex) - Finally - '清理文件 - Try - If Installer IsNot Nothing Then Installer.Dispose() - If File.Exists(InstallerAddress) Then File.Delete(InstallerAddress) - Catch ex As Exception - Log(ex, "安装 Forge 清理文件时出错") - End Try - End Try - End Sub) With {.ProgressWeight = 10}) + If DeltaList.Count > 1 Then + '它可能和 OptiFine 安装同时运行,导致增加的文件不止一个(这导致了 #151) + '也可能是因为 Forge 安装器的 Bug,生成了一个名字错误的文件夹,所以需要检查文件夹是否为空 + DeltaList = DeltaList.Where(Function(l) l.Name.Contains("forge") AndAlso l.EnumerateFiles.Any).ToList + End If + If DeltaList.Count = 1 Then '如果没有新增文件夹,那么预测的文件夹名就是正确的 + Dim JsonFile As FileInfo = DeltaList(0).EnumerateFiles.First() + WriteFile(VersionFolder & Id & ".json", ReadFile(JsonFile.FullName)) + End If + '新建 mods 文件夹 + Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\") + Catch ex As Exception + Throw New Exception("安装新 Forge 版本失败", ex) + Finally + '清理文件 + Try + If Installer IsNot Nothing Then Installer.Dispose() + If File.Exists(InstallerAddress) Then File.Delete(InstallerAddress) + Catch ex As Exception + Log(ex, "安装 Forge 清理文件时出错") + End Try + End Try + End Sub) With {.ProgressWeight = 10}) Else Log("[Download] 检测为非新版 Forge:" & Version) Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("安装 Mod 加载器(方式 B)", - Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) - Dim Installer As ZipArchive = Nothing - Try - '解压并获取信息 - Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open)) - Task.Progress = 0.2 - Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open)) - Task.Progress = 0.4 - '新建版本文件夹 - Directory.CreateDirectory(VersionFolder) - Task.Progress = 0.5 - If Json("install") Is Nothing Then - '中版格式 - Log("[Download] 进行中版方式安装:" & InstallerAddress) - '建立 Json 文件 - Dim JsonVersion As JObject = GetJson(ReadFile(Installer.GetEntry(Json("json").ToString.TrimStart("/")).Open)) - JsonVersion("id") = Id - WriteFile(VersionFolder & Id & ".json", JsonVersion.ToString) - Task.Progress = 0.6 - '解压支持库文件 - Installer.Dispose() - ExtractFile(InstallerAddress, InstallerAddress & "_unrar\") - CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\") - DeleteDirectory(InstallerAddress & "_unrar\") - Else - '旧版格式 - Log("[Download] 进行旧版方式安装:" & InstallerAddress) - '解压 Jar 文件 - Dim JarAddress As String = McLibGet(Json("install")("path"), CustomMcFolder:=McFolder) - If File.Exists(JarAddress) Then File.Delete(JarAddress) - WriteFile(JarAddress, Installer.GetEntry(Json("install")("filePath")).Open) - Task.Progress = 0.9 - '建立 Json 文件 - Json("versionInfo")("id") = Id - If Json("versionInfo")("inheritsFrom") Is Nothing Then CType(Json("versionInfo"), JObject).Add("inheritsFrom", Inherit) - WriteFile(VersionFolder & Id & ".json", Json("versionInfo").ToString) - End If - '新建 mods 文件夹 - Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\") - Catch ex As Exception - Throw New Exception("非新版方式安装 Forge 失败", ex) - Finally - Try - '清理文件 - If Installer IsNot Nothing Then Installer.Dispose() - If File.Exists(InstallerAddress) Then File.Delete(InstallerAddress) - If Directory.Exists(InstallerAddress & "_unrar\") Then DeleteDirectory(InstallerAddress & "_unrar\") - Catch ex As Exception - Log(ex, "非新版方式安装 Forge 清理文件时出错") - End Try - End Try - End Sub) With {.ProgressWeight = 1}) + Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) + Dim Installer As ZipArchive = Nothing + Try + '解压并获取信息 + Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open)) + Task.Progress = 0.2 + Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open)) + Task.Progress = 0.4 + '新建版本文件夹 + Directory.CreateDirectory(VersionFolder) + Task.Progress = 0.5 + If Json("install") Is Nothing Then + '中版格式 + Log("[Download] 进行中版方式安装:" & InstallerAddress) + '建立 Json 文件 + Dim JsonVersion As JObject = GetJson(ReadFile(Installer.GetEntry(Json("json").ToString.TrimStart("/")).Open)) + JsonVersion("id") = Id + WriteFile(VersionFolder & Id & ".json", JsonVersion.ToString) + Task.Progress = 0.6 + '解压支持库文件 + Installer.Dispose() + ExtractFile(InstallerAddress, InstallerAddress & "_unrar\") + CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\") + DeleteDirectory(InstallerAddress & "_unrar\") + Else + '旧版格式 + Log("[Download] 进行旧版方式安装:" & InstallerAddress) + '解压 Jar 文件 + Dim JarAddress As String = McLibGet(Json("install")("path"), CustomMcFolder:=McFolder) + If File.Exists(JarAddress) Then File.Delete(JarAddress) + WriteFile(JarAddress, Installer.GetEntry(Json("install")("filePath")).Open) + Task.Progress = 0.9 + '建立 Json 文件 + Json("versionInfo")("id") = Id + If Json("versionInfo")("inheritsFrom") Is Nothing Then CType(Json("versionInfo"), JObject).Add("inheritsFrom", Inherit) + WriteFile(VersionFolder & Id & ".json", Json("versionInfo").ToString) + End If + '新建 mods 文件夹 + Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\") + Catch ex As Exception + Throw New Exception("非新版方式安装 Forge 失败", ex) + Finally + Try + '清理文件 + If Installer IsNot Nothing Then Installer.Dispose() + If File.Exists(InstallerAddress) Then File.Delete(InstallerAddress) + If Directory.Exists(InstallerAddress & "_unrar\") Then DeleteDirectory(InstallerAddress & "_unrar\") + Catch ex As Exception + Log(ex, "非新版方式安装 Forge 清理文件时出错") + End Try + End Try + End Sub) With {.ProgressWeight = 1}) If FixLibrary Then If IsCustomFolder Then Throw New Exception("若需要补全支持库,就不能自定义 MC 文件夹") Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 Mod 加载器支持库文件", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj index 9eb1b72..4df8c36 100644 --- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj +++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj @@ -913,5 +913,8 @@ + + + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip index 5a3fbe3fd5b02f22f27ecc22ebd76202cd722591..d00647ec0ce0fbe0209d73dfe225a34086d5d423 100644 GIT binary patch delta 5482 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 AYXATM diff --git a/Plain Craft Launcher 2/Resources/forge-installer.jar b/Plain Craft Launcher 2/Resources/forge-installer.jar index 1f650adfb1d09b86824bb87248401ce577e9b9d1..bc54f90ce44241082ae1fa034ad6ae001ecae3bf 100644 GIT binary patch delta 2970 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