From 7ba79db82c59332dccc9f1f638c121800fc5d206 Mon Sep 17 00:00:00 2001 From: Pigeon0v0 Date: Sat, 15 Jun 2024 21:34:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(neoforge):=20=E6=B7=BB=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=20NeoForge=20=E7=9A=84=E5=AE=89=E8=A3=85=E9=80=89=E9=A1=B9?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=94=AF=E6=8C=81=E8=8E=B7=E5=8F=96=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modules/Minecraft/ModDownload.vb | 226 +++++++ .../Pages/PageDownload/ModDownloadLib.vb | 624 ++++++++++++++++++ .../PageDownload/PageDownloadInstall.xaml | 19 + .../PageDownload/PageDownloadInstall.xaml.vb | 134 +++- 4 files changed, 1002 insertions(+), 1 deletion(-) diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb index 10cfdbb..9e9ea6a 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb @@ -721,6 +721,232 @@ #End Region +#Region "DlNeoForgeVersion | NeoForge 版本列表" + + Public Class DlNeoForgeVersionEntry + ''' + ''' 完整的版本名,如 “14.22.1.2478”。 + ''' + Public Version As String + ''' + ''' 对应的 Minecraft 版本,如“1.12.2”。 + ''' + Public Inherit As String + ''' + ''' 发布时间,格式为“yyyy/MM/dd HH:mm”。 + ''' + Public ReleaseTime As String + ''' + ''' 文件的 MD5 或 SHA1(BMCLAPI 的老版本是 MD5,新版本是 SHA1;官方源总是 MD5)。 + ''' + Public Hash As String = Nothing + ''' + ''' 安装类型。有 installer、client、universal 三种。 + ''' + Public Category As String + ''' + ''' 版本分支。若无分支则为 Nothing。 + ''' + Public Branch As String = Nothing + Public Structure DlNeoForgeListResult + ''' + ''' 数据来源名称,如“Official”,“BMCLAPI”。 + ''' + Public SourceName As String + ''' + ''' 是否为官方的实时数据。 + ''' + Public IsOfficial As Boolean + ''' + ''' 获取到的数据。 + ''' + Public Value As JObject + End Structure + ''' + ''' 是否为新版 Forge。(即为 Minecraft 1.13+) + ''' + Public ReadOnly Property IsNewType As Boolean + Get + Return Version.Split(".")(0) >= 20 + End Get + End Property + ''' + ''' 构建数。 + ''' + Public ReadOnly Property Build As Integer + Get + Dim Version = Me.Version.Split(".") + If Version(0) < 15 Then + Return Version(Version.Count - 1) + Else + Return Version(Version.Count - 1) + 10000 + End If + End Get + End Property + ''' + ''' 用于下载的文件版本名。可能在 Version 的基础上添加了分支。 + ''' + Public ReadOnly Property FileVersion As String + Get + Return Version & If(Branch Is Nothing, "", "-" & Branch) + End Get + End Property + ''' + ''' 即将下载的文件全名。 + ''' + Public ReadOnly Property FileName As String + Get + Return "forge-" & Inherit & "-" & FileVersion & "-" & Category & "." & FileSuffix + End Get + End Property + ''' + ''' 文件扩展名。 + ''' + Public ReadOnly Property FileSuffix As String + Get + If Category = "installer" Then + Return "jar" + Else + Return "zip" + End If + End Get + End Property + End Class + + ''' + ''' NeoForge 版本列表,主加载器。 + ''' + Public Sub DlNeoForgeVersionMain(Loader As LoaderTask(Of String, List(Of DlNeoForgeVersionEntry))) + Dim DlNeoForgeVersionOfficialLoader As New LoaderTask(Of String, List(Of DlNeoForgeVersionEntry))("DlNeoForgeVersion Official", AddressOf DlNeoForgeVersionOfficialMain) + Dim DlNeoForgeVersionBmclapiLoader As New LoaderTask(Of String, List(Of DlNeoForgeVersionEntry))("DlNeoForgeVersion Bmclapi", AddressOf DlNeoForgeVersionBmclapiMain) + Select Case Setup.Get("ToolDownloadVersion") + Case 0 + DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)) From { + New KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)(DlNeoForgeVersionBmclapiLoader, 30), + New KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)(DlNeoForgeVersionOfficialLoader, 60) + }, Loader.IsForceRestarting) + Case 1 + DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)) From { + New KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)(DlNeoForgeVersionOfficialLoader, 5), + New KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)(DlNeoForgeVersionBmclapiLoader, 35) + }, Loader.IsForceRestarting) + Case Else + DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)) From { + New KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)(DlNeoForgeVersionOfficialLoader, 60), + New KeyValuePair(Of LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)), Integer)(DlNeoForgeVersionBmclapiLoader, 60) + }, Loader.IsForceRestarting) + End Select + End Sub + + ''' + ''' NeoForge 版本列表,官方源。 + ''' + Public Sub DlNeoForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlNeoForgeVersionEntry))) + Dim Result As String + Dim ResultJson As JObject + Try + Result = NetGetCodeByDownload("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True) + Catch ex As Exception + If GetExceptionSummary(ex).Contains("(404)") Then + Throw New Exception("没有可用版本") + Else + Throw + End If + End Try + If Result.Length < 50 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") + ResultJson = GetJson(Result) + Dim Versions As New List(Of DlNeoForgeVersionEntry) + Dim VersionList As String = ResultJson("versions") + MyMsgBox(VersionList, "VersionList 获取测试") + 'Try + ' Try + ' '基础信息获取 + ' Dim Name As String = RegexSeek(VersionCode, "(?<=[^(0-9)]+)[0-9\.]+") + ' Dim IsRecommended As Boolean = VersionCode.Contains("fa promo-recommended") + ' Dim Inherit As String = Loader.Input + ' '分支获取 + ' Dim Branch As String = RegexSeek(VersionCode, $"(?<=-{Name}-)[^-""]+(?=-[a-z]+.[a-z]{{3}})") + ' If String.IsNullOrWhiteSpace(Branch) Then Branch = Nothing + ' '发布时间获取 + ' Dim ReleaseTimeOriginal = RegexSeek(VersionCode, "(?<=""download-time"" title="")[^""]+") + ' Dim ReleaseTimeSplit = ReleaseTimeOriginal.Split(" -:".ToCharArray) '原格式:"2021-02-15 03:24:02" + ' Dim ReleaseDate As New Date(ReleaseTimeSplit(0), ReleaseTimeSplit(1), ReleaseTimeSplit(2), '年月日 + ' ReleaseTimeSplit(3), ReleaseTimeSplit(4), ReleaseTimeSplit(5), '时分秒 + ' 0, DateTimeKind.Utc) '以 UTC 时间作为标准 + ' Dim ReleaseTime As String = ReleaseDate.ToLocalTime.ToString("yyyy'/'MM'/'dd HH':'mm") '时区与格式转换 + ' '添加进列表 + ' Versions.Add(New DlNeoForgeVersionEntry With {.Category = Category, .Version = Name, .Hash = MD5.Trim(vbCr, vbLf), .Inherit = Inherit, .ReleaseTime = ReleaseTime, .Branch = Branch}) + ' Catch ex As Exception + ' Throw New Exception("版本信息提取失败(" & VersionCode & ")", ex) + ' End Try + 'Catch ex As Exception + ' Throw New Exception("版本列表解析失败(" & Result & ")", ex) + 'End Try + If Not Versions.Any() Then Throw New Exception("没有可用版本") + Loader.Output = Versions + End Sub + + ''' + ''' NeoForge 版本列表,BMCLAPI。 + ''' + Public Sub DlNeoForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlNeoForgeVersionEntry))) + Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/neoforge/list/" & Loader.Input, IsJson:=True) + Dim Versions As New List(Of DlNeoForgeVersionEntry) + Try + For Each Token As JObject In Json + Dim Name As String = Token("version") + Dim Inherit As String = Token("mcversion") + Dim Entry = New DlNeoForgeVersionEntry With {.Version = Name, .Inherit = Inherit} + Versions.Add(Entry) + Next + 'For Each Token As JObject In Json + ' '分类与 Hash 获取 + ' Dim Hash As String = Nothing, Category As String = "unknown", Proi As Integer = -1 + ' For Each File As JObject In Token("files") + ' Select Case File("category").ToString + ' Case "installer" + ' If File("format").ToString = "jar" Then + ' '类型为 installer.jar,支持范围 ~753 (~ 1.6.1 部分), 738~684 (1.5.2 全部) + ' Hash = File("hash") + ' Category = "installer" + ' Proi = 2 + ' End If + ' Case "universal" + ' If Proi <= 1 AndAlso File("format").ToString = "zip" Then + ' '类型为 universal.zip,支持范围 751~449 (1.6.1 部分), 682~183 (1.5.1 ~ 1.3.2 部分) + ' Hash = File("hash") + ' Category = "universal" + ' Proi = 1 + ' End If + ' Case "client" + ' If Proi <= 0 AndAlso File("format").ToString = "zip" Then + ' '类型为 client.zip,支持范围 182~ (1.3.2 部分 ~) + ' Hash = File("hash") + ' Category = "client" + ' Proi = 0 + ' End If + ' End Select + ' Next + ' '获取 Entry + ' Dim Inherit As String = Loader.Input + ' Dim Branch As String = Token("branch") + ' Dim Name As String = Token("version") + ' '基础信息获取 + ' Dim Entry = New DlNeoForgeVersionEntry With {.Hash = Hash, .Category = Category, .Version = Name, .Branch = Branch, .Inherit = Inherit} + ' Dim TimeSplit = Token("modified").ToString.Split("-"c, "T"c, ":"c, "."c, " "c, "/"c) + ' Entry.ReleaseTime = Token("modified").ToObject(Of Date).ToLocalTime.ToString("yyyy'/'MM'/'dd HH':'mm") + ' '添加项 + ' Versions.Add(Entry) + 'Next + Catch ex As Exception + Throw New Exception("版本列表解析失败(" & Json.ToString & ")", ex) + End Try + If Not Versions.Any() Then Throw New Exception("没有可用版本") + Loader.Output = Versions + End Sub + +#End Region + #Region "DlLiteLoaderList | LiteLoader 版本列表" Public Structure DlLiteLoaderListResult diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index 3b02dc6..6f6a331 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -1696,6 +1696,630 @@ Retry: #End Region +#Region "NeoForge 下载" + + Public Sub McDownloadNeoForge(DownloadInfo As DlNeoForgeVersionEntry) + '老版本提示 + If DownloadInfo.Category = "client" Then + If MyMsgBox("该 Forge 版本过于古老,PCL 暂不支持该版本的自动安装。" & vbCrLf & + "若你仍然希望继续,PCL 将把安装程序下载到你指定的位置,但不会进行安装。", + "版本过老", "继续", "取消") = 1 Then + McDownloadNeoForgeSave(DownloadInfo) + End If + Exit Sub + End If + If DownloadInfo.Category = "universal" OrElse DownloadInfo.Inherit.StartsWithF("1.5") Then '对该版本自动安装的支持将在之后加入 + If MyMsgBox("该 Forge 版本过于古老,PCL 暂不支持该版本的自动安装。" & vbCrLf & + "若你仍然希望继续,PCL 将把安装程序下载到你指定的位置,但不会进行安装。", + "版本过老", "继续", "取消") = 1 Then + McDownloadNeoForgeSave(DownloadInfo) + End If + Exit Sub + End If + '初始化参数 + Dim Id As String = DownloadInfo.Inherit & "-neoforge-" & DownloadInfo.Version + Dim Target As String = PathTemp & "Cache\Code\NeoForgeInstall-" & DownloadInfo.Version & "_" & GetUuid() & "." & DownloadInfo.FileSuffix + Dim VersionFolder As String = PathMcFolder & "versions\" & Id & "\" + Dim DisplayName As String = "Forge " & DownloadInfo.Inherit & " - " & DownloadInfo.Version + Try + + '重复任务检查 + SyncLock LoaderTaskbarLock + For i = 0 To LoaderTaskbar.Count - 1 + If LoaderTaskbar(i).Name = DisplayName & " 下载" Then + Hint("该版本正在下载中!", HintType.Critical) + Exit Sub + End If + Next + End SyncLock + + '已有版本检查 + If File.Exists(VersionFolder & Id & ".json") Then + If MyMsgBox("版本 " & Id & " 已存在,是否重新下载?" & vbCrLf & "这会覆盖版本的 json 和 jar 文件,但不会影响版本隔离的文件。", "版本已存在", "继续", "取消") = 1 Then + File.Delete(VersionFolder & Id & ".jar") + File.Delete(VersionFolder & Id & ".json") + Else + Exit Sub + End If + End If + + '启动 + Dim Loader As New LoaderCombo(Of String)(DisplayName & " 下载", McDownloadNeoForgeLoader(DownloadInfo.Version, DownloadInfo.Inherit, DownloadInfo)) With {.OnStateChanged = AddressOf McInstallState} + Loader.Start(VersionFolder) + LoaderTaskbarAdd(Loader) + FrmMain.BtnExtraDownload.ShowRefresh() + FrmMain.BtnExtraDownload.Ribble() + + Catch ex As Exception + Log(ex, "开始 NeoForge 下载失败", LogLevel.Feedback) + Finally + '删除安装包 + Try + If File.Exists(Target) Then File.Delete(Target) + Catch + End Try + End Try + End Sub + Public Sub McDownloadNeoForgeSave(DownloadInfo As DlNeoForgeVersionEntry) + Try + Dim Target As String = SelectAs("选择保存位置", DownloadInfo.FileName, "NeoForge 文件 (*." & DownloadInfo.FileSuffix & ")|*." & DownloadInfo.FileSuffix) + Dim DisplayName As String = "Forge " & DownloadInfo.Inherit & " - " & DownloadInfo.Version + If Not Target.Contains("\") Then Exit Sub + + '重复任务检查 + SyncLock LoaderTaskbarLock + For i = 0 To LoaderTaskbar.Count - 1 + If LoaderTaskbar(i).Name = DisplayName & " 下载" Then + Hint("该版本正在下载中!", HintType.Critical) + Exit Sub + End If + Next + End SyncLock + + '构造步骤加载器 + 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://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}) + + '启动 + Dim Loader As New LoaderCombo(Of DlForgeVersionEntry)(DisplayName & " 下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave} + Loader.Start(DownloadInfo) + LoaderTaskbarAdd(Loader) + FrmMain.BtnExtraDownload.ShowRefresh() + FrmMain.BtnExtraDownload.Ribble() + + Catch ex As Exception + Log(ex, "开始 NeoForge 下载失败", LogLevel.Feedback) + End Try + End Sub + + Private Sub NeoForgeInjector(Target As String, Task As LoaderTask(Of Boolean, Boolean), McFolder As String, UseJavaWrapper As Boolean) + '选择 Java + Dim Java As JavaEntry + SyncLock JavaLock + Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 60)) + If Java Is Nothing Then + If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。") + '开始自动下载 + Dim JavaLoader = JavaFixLoaders(17) + Try + JavaLoader.Start(17, IsForceRestart:=True) + Do While JavaLoader.State = LoadState.Loading AndAlso Not Task.IsAborted + Thread.Sleep(10) + Loop + Finally + JavaLoader.Abort() '确保取消时中止 Java 下载 + End Try + '检查下载结果 + 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 + End SyncLock + '添加 Java Wrapper 作为主 Jar + Dim Arguments As String + If UseJavaWrapper Then + Arguments = $"-Doolloo.jlw.tmpdir=""{GetJavaWrapperDir()}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{ExtractJavaWrapper()}"" com.bangbang93.ForgeInstaller ""{McFolder}" + Else + Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}" + End If + If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments + '开始启动 + SyncLock InstallSyncLock + Dim Info = New ProcessStartInfo With { + .FileName = Java.PathJavaw, + .Arguments = Arguments, + .UseShellExecute = False, + .CreateNoWindow = True, + .RedirectStandardError = True, + .RedirectStandardOutput = True + } + Log("[Download] 开始安装 NeoForge:" & Arguments) + Dim process As New Process With {.StartInfo = Info} + 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() + NeoForgeInjectorLine(e.Data, Task) + End If + Catch ex As ObjectDisposedException + Catch ex As Exception + Log(ex, "读取 NeoForge 安装器信息失败") + End Try + Try + If Task.State = LoadState.Aborted AndAlso Not process.HasExited Then + Log("[Installer] 由于任务取消,已中止 NeoForge 安装") + 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() + '等待 + Do Until process.HasExited + Thread.Sleep(10) + Loop + '输出 + outputWaitHandle.WaitOne(10000) + errorWaitHandle.WaitOne(10000) + process.Dispose() + '检查是否安装成功:最后两行中是否有 true(true 可能在倒数第二行,见 #832) + If LastResults.Last = "true" OrElse (LastResults.Count >= 2 AndAlso LastResults(LastResults.Count - 2) = "true") Then Exit Sub + Log(Join(LastResults, vbCrLf)) + Dim LastLines As String = "" + For i As Integer = Math.Max(0, LastResults.Count - 5) To LastResults.Count - 1 '最后 5 行 + LastLines &= vbCrLf & LastResults(i) + Next + Throw New Exception("Forge 安装器出错,日志结束部分为:" & LastLines) + End Using + End Using + End SyncLock + End Sub + Private Sub NeoForgeInjectorLine(Content As String, Task As LoaderTask(Of Boolean, Boolean)) + If Content.StartsWithF(" Data") OrElse Content.StartsWithF(" Slim") Then + If ModeDebug Then Log("[Installer] " & Content) + ElseIf Content.StartsWithF(" Reading patch ") Then + If ModeDebug Then Log("[Installer] " & Content) + Task.Progress = 0.86 + Else + Select Case Content + Case "Extracting json" + Log("[Installer] " & Content) + Task.Progress = 0.07 + Case "Downloading libraries" + Log("[Installer] " & Content) + Task.Progress = 0.08 + Case " File exists: Checksum validated." + If ModeDebug Then Log("[Installer] " & Content) + Task.Progress += 0.003 + Case "Building Processors" + Task.Progress = 0.38 + Case "Task: DOWNLOAD_MOJMAPS" 'B + Task.Progress = 0.4 + Case "Task: MERGE_MAPPING" 'B + Task.Progress = 0.6 + Case "Splitting: " + Task.Progress = 0.62 + Case "Parameter Annotations" 'B + Task.Progress = 0.66 + Case "Processing Complete" 'B + Task.Progress = 0.7 + Case "Remapping final jar" 'A + Task.Progress = 0.72 + Case "Remapping jar... 50%" 'A + Task.Progress = 0.76 + Case "Remapping jar... 100%" 'A + Task.Progress = 0.81 + Case "Injecting profile" + Task.Progress = 0.91 + Case Else + If ModeDebug Then Log("[Installer] " & Content) + Exit Sub + End Select + Log("[Installer] " & Content) + End If + End Sub + + ''' + ''' 获取下载某个 NeoForge 版本的加载器列表。 + ''' + Private Function McDownloadNeoForgeLoader(Version As String, Inherit As String, Optional DownloadInfo As DlNeoForgeVersionEntry = Nothing, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional ClientFolder As String = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase) + + '参数初始化 + McFolder = If(McFolder, PathMcFolder) + If Version.StartsWithF("1.") AndAlso Version.Contains("-") Then + '类似 1.19.3-41.2.8 格式,优先使用 Version 中要求的版本而非 Inherit(例如 1.19.3 却使用了 1.19 的 Forge) + Inherit = Version.Before("-") + Version = Version.After("-") + End If + Dim IsCustomFolder As Boolean = McFolder <> PathMcFolder + Dim Id As String = Inherit & "-forge-" & Version + Dim InstallerAddress As String = PathTemp & "Cache\Code\NeoForgeInstall-" & Version & "_" & RandomInteger(0, 100000) + Dim VersionFolder As String = McFolder & "versions\" & Id & "\" + Dim DisplayName As String = "Forge " & Inherit & " - " & Version + Dim Loaders As New List(Of LoaderBase) + Dim LibVersionFolder As String = PathMcFolder & "versions\" & Id & "\" '作为 Lib 文件目标的版本文件夹 + + '获取下载信息 + If DownloadInfo Is Nothing Then + Loaders.Add(New LoaderTask(Of String, String)("获取 Mod 加载器详细信息", + Sub(Task As LoaderTask(Of String, String)) + '获取 Forge 版本列表 + Dim NeoForgeLoader = New LoaderTask(Of String, List(Of DlNeoForgeVersionEntry))("McDownloadNeoForgeLoader " & Inherit, AddressOf DlNeoForgeVersionMain) + NeoForgeLoader.WaitForExit(Inherit) + Task.Progress = 0.8 + '查找对应版本 + For Each NeoForgeVersion In NeoForgeLoader.Output + If NeoForgeVersion.Version = Version Then + DownloadInfo = NeoForgeVersion + Exit Sub + End If + Next + Throw New Exception("未能找到 NeoForge " & Inherit & "-" & Version & " 的详细信息!") + End Sub) With {.ProgressWeight = 3}) + End If + '下载 Forge 主文件 + Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("准备 Mod 加载器下载", + Sub(Task As LoaderTask(Of String, List(Of NetFile))) + '启动依赖版本的下载 + If ClientDownloadLoader Is Nothing Then + If IsCustomFolder Then Throw New Exception("如果没有指定原版下载器,则不能指定 MC 安装文件夹") + ClientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, Inherit) + End If + '添加主文件 + 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 + }, InstallerAddress, New FileChecker(MinSize:=64 * 1024, Hash:=DownloadInfo.Hash))} + Task.Output = Files + End Sub) With {.ProgressWeight = 0.5, .Show = False}) + Loaders.Add(New LoaderDownload("下载 Mod 加载器主文件", New List(Of NetFile)) With {.ProgressWeight = 9}) + + '安装(仅在新版安装时需要原版 Jar) + If Version.Before(".") >= 20 Then + Log("[Download] 检测为新版 NeoForge:" & 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()).Before("@") + 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.EndsWithF("forge-" & Inherit & "-" & Version & ".jar") OrElse + Libs(i).LocalPath.EndsWithF("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}) + 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)) +#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 +#End Region +#Region "原版文件" + '等待原版文件下载完成 + 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.Any Then Log("[Download] Forge 安装正在等待原版文件下载完成") + Do While TargetLoaders.Any 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}) + 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. + 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 +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. + 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}) + 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}) + 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}) + Loaders.Add(New LoaderDownload("下载 Mod 加载器支持库文件", New List(Of NetFile)) With {.ProgressWeight = 11}) + End If + End If + + Return Loaders + End Function + +#End Region + +#Region "NeoForge 下载菜单" + + Public Sub NeoForgeDownloadListItemPreload(Stack As StackPanel, Entrys As List(Of DlNeoForgeVersionEntry), OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) + '获取最新版本 + Dim FreshVersion As DlNeoForgeVersionEntry = Nothing + If Entrys.Any Then + FreshVersion = Entrys(0) + Else + Log("[System] 未找到可用的 NeoForge 版本", LogLevel.Debug) + End If + Dim RecommendedVersion As DlNeoForgeVersionEntry = Nothing + '显示各个版本 + If FreshVersion IsNot Nothing Then + Dim Fresh = NeoForgeDownloadListItem(FreshVersion, OnClick, IsSaveOnly) + Fresh.Info = "最新版" & If(Fresh.Info = "", "", "," & Fresh.Info) + Stack.Children.Add(Fresh) + End If + '添加间隔 + Stack.Children.Add(New TextBlock With {.Text = "全部版本 (" & Entrys.Count & ")", .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 13, 0, 4)}) + End Sub + Public Function NeoForgeDownloadListItem(Entry As DlNeoForgeVersionEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem + '建立控件 + Dim NewItem As New MyListItem With { + .Title = Entry.Version, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, + .Info = If(Entry.ReleaseTime = "", + If(ModeDebug, "种类:" & Entry.Category & If(Entry.Branch Is Nothing, "", ",开发分支:" & Entry.Branch), ""), + "发布于 " & Entry.ReleaseTime & If(ModeDebug, ",种类:" & Entry.Category & If(Entry.Branch Is Nothing, "", ",开发分支:" & Entry.Branch), "")), + .Logo = PathImage & "Blocks/Anvil.png" + } + AddHandler NewItem.Click, OnClick + '建立菜单 + If IsSaveOnly Then + NewItem.ContentHandler = AddressOf NeoForgeSaveContMenuBuild + Else + NewItem.ContentHandler = AddressOf NeoForgeContMenuBuild + End If + '结束 + Return NewItem + End Function + Private Sub NeoForgeContMenuBuild(sender As MyListItem, e As EventArgs) + Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"} + ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center) + ToolTipService.SetVerticalOffset(BtnSave, 30) + ToolTipService.SetHorizontalOffset(BtnSave, 2) + AddHandler BtnSave.Click, AddressOf NeoForgeSave_Click + Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"} + ToolTipService.SetPlacement(BtnInfo, Primitives.PlacementMode.Center) + ToolTipService.SetVerticalOffset(BtnInfo, 30) + ToolTipService.SetHorizontalOffset(BtnInfo, 2) + AddHandler BtnInfo.Click, AddressOf NeoForgeLog_Click + sender.Buttons = {BtnSave, BtnInfo} + End Sub + Private Sub NeoForgeSaveContMenuBuild(sender As MyListItem, e As EventArgs) + Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"} + ToolTipService.SetPlacement(BtnInfo, Primitives.PlacementMode.Center) + ToolTipService.SetVerticalOffset(BtnInfo, 30) + ToolTipService.SetHorizontalOffset(BtnInfo, 2) + AddHandler BtnInfo.Click, AddressOf NeoForgeLog_Click + sender.Buttons = {BtnInfo} + End Sub + Private Sub NeoForgeLog_Click(sender As Object, e As RoutedEventArgs) + Dim Version As DlForgeVersionEntry + If sender.Tag IsNot Nothing Then + Version = sender.Tag + ElseIf sender.Parent.Tag IsNot Nothing Then + Version = sender.Parent.Tag + Else + Version = sender.Parent.Parent.Tag + End If + OpenWebsite("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" & Version.Inherit & "-" & Version.Version & "/forge-" & Version.Inherit & "-" & Version.Version & "-changelog.txt") + End Sub + Public Sub NeoForgeSave_Click(sender As Object, e As RoutedEventArgs) + Dim Version As DlForgeVersionEntry + If sender.Tag IsNot Nothing Then + Version = sender.Tag + ElseIf sender.Parent.Tag IsNot Nothing Then + Version = sender.Parent.Tag + Else + Version = sender.Parent.Parent.Tag + End If + McDownloadForgeSave(Version) + End Sub + +#End Region + #Region "Fabric 下载" Public Sub McDownloadFabricLoaderSave(DownloadInfo As JObject) diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml index b6b6dc5..69c47f3 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml @@ -79,6 +79,24 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> + + + + + + + + + + + + + + + + + @@ -178,6 +196,7 @@ + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb index d7ef0bc..f703ef4 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb @@ -51,6 +51,7 @@ CardOptiFine.IsSwaped = True CardLiteLoader.IsSwaped = True CardForge.IsSwaped = True + CardNeoForge.IsSwaped = True CardFabric.IsSwaped = True CardFabricApi.IsSwaped = True CardOptiFabric.IsSwaped = True @@ -73,6 +74,13 @@ ForgeLoader.Start(SelectedMinecraftId) End If + '启动 NeoForge 加载 + If SelectedMinecraftId.StartsWith("1.") Then + Dim NeoForgeLoader = New LoaderTask(Of String, List(Of DlNeoForgeVersionEntry))("DlNeoForgeVersion " & SelectedMinecraftId, AddressOf DlNeoForgeVersionMain) + LoadNeoForge.State = NeoForgeLoader + NeoForgeLoader.Start(SelectedMinecraftId) + End If + '启动 Fabric API、OptiFabric 加载 DlFabricApiLoader.Start() DlOptiFabricLoader.Start() @@ -86,6 +94,7 @@ OptiFine_Loaded() LiteLoader_Loaded() Forge_Loaded() + NeoForge_Loaded() Fabric_Loaded() FabricApi_Loaded() OptiFabric_Loaded() @@ -102,6 +111,7 @@ BtnOptiFineClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFine.MainTextBlock, .Mode = BindingMode.OneWay}) BtnLiteLoaderClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardLiteLoader.MainTextBlock, .Mode = BindingMode.OneWay}) BtnForgeClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardForge.MainTextBlock, .Mode = BindingMode.OneWay}) + BtnNeoForgeClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardNeoForge.MainTextBlock, .Mode = BindingMode.OneWay}) BtnFabricClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardFabric.MainTextBlock, .Mode = BindingMode.OneWay}) BtnFabricApiClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardFabricApi.MainTextBlock, .Mode = BindingMode.OneWay}) BtnOptiFabricClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFabric.MainTextBlock, .Mode = BindingMode.OneWay}) @@ -213,6 +223,26 @@ End If End Sub + 'NeoForge + Private SelectedNeoForge As DlNeoForgeVersionEntry = Nothing + Private Sub SetNeoForgeInfoShow(IsShow As String) + If PanNeoForgeInfo.Tag = IsShow Then Exit Sub + PanNeoForgeInfo.Tag = IsShow + If IsShow = "True" Then + '显示信息栏 + AniStart({ + AaTranslateY(PanNeoForgeInfo, -CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack), + AaOpacity(PanNeoForgeInfo, 1 - PanNeoForgeInfo.Opacity, 100, 90) + }, "SetNeoForgeInfoShow") + Else + '隐藏信息栏 + AniStart({ + AaTranslateY(PanNeoForgeInfo, 6 - CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 200), + AaOpacity(PanNeoForgeInfo, -PanNeoForgeInfo.Opacity, 100) + }, "SetNeoForgeInfoShow") + End If + End Sub + 'Fabric Private SelectedFabric As String = Nothing Private Sub SetFabricInfoShow(IsShow As String) @@ -277,7 +307,7 @@ ''' ''' 重载已选择的项目的显示。 ''' - Private Sub SelectReload() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged + Private Sub SelectReload() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged If SelectedMinecraftId Is Nothing OrElse IsReloading Then Exit Sub IsReloading = True '主预览 @@ -341,6 +371,22 @@ LabForge.Text = SelectedForge.Version LabForge.Foreground = ColorGray1 End If + 'NeoForge + Dim NeoForgeError As String = LoadNeoForgeGetError() + CardNeoForge.MainSwap.Visibility = If(NeoForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) + If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwaped = True + SetNeoForgeInfoShow(CardNeoForge.IsSwaped) + If SelectedNeoForge Is Nothing Then + BtnNeoForgeClear.Visibility = Visibility.Collapsed + ImgNeoForge.Visibility = Visibility.Collapsed + LabNeoForge.Text = If(NeoForgeError, "点击选择") + LabNeoForge.Foreground = ColorGray4 + Else + BtnNeoForgeClear.Visibility = Visibility.Visible + ImgNeoForge.Visibility = Visibility.Visible + LabNeoForge.Text = SelectedNeoForge.Version + LabNeoForge.Foreground = ColorGray1 + End If 'Fabric If SelectedMinecraftId.Contains("1.") AndAlso Val(SelectedMinecraftId.Split(".")(1)) <= 13 Then CardFabric.Visibility = Visibility.Collapsed @@ -434,6 +480,7 @@ SelectedOptiFine = Nothing SelectedLiteLoader = Nothing SelectedForge = Nothing + SelectedNeoForge = Nothing SelectedFabric = Nothing SelectedFabricApi = Nothing SelectedOptiFabric = Nothing @@ -451,6 +498,9 @@ If SelectedForge IsNot Nothing Then Name += "-Forge_" & SelectedForge.Version End If + If SelectedNeoForge IsNot Nothing Then + Name += "-NeoForge_" & SelectedNeoForge.Version + End If If SelectedLiteLoader IsNot Nothing Then Name += "-LiteLoader" End If @@ -470,6 +520,9 @@ If SelectedForge IsNot Nothing Then Info += ", Forge " & SelectedForge.Version End If + If SelectedNeoForge IsNot Nothing Then + Info += ", NeoForge " & SelectedNeoForge.Version + End If If SelectedLiteLoader IsNot Nothing Then Info += ", LiteLoader" End If @@ -487,6 +540,8 @@ Return "pack://application:,,,/images/Blocks/Fabric.png" ElseIf SelectedForge IsNot Nothing Then Return "pack://application:,,,/images/Blocks/Anvil.png" + ElseIf SelectedNeoForge IsNot Nothing Then + Return "pack://application:,,,/images/Blocks/Anvil.png" ElseIf SelectedLiteLoader IsNot Nothing Then Return "pack://application:,,,/images/Blocks/Egg.png" ElseIf SelectedOptiFine IsNot Nothing Then @@ -706,6 +761,7 @@ If SelectedForge IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, SelectedForge) Then SelectedForge = Nothing OptiFabric_Loaded() Forge_Loaded() + NeoForge_Loaded() CardOptiFine.IsSwaped = True SelectReload() End Sub @@ -715,6 +771,7 @@ CardOptiFine.IsSwaped = True e.Handled = True Forge_Loaded() + NeoForge_Loaded() SelectReload() End Sub @@ -866,6 +923,81 @@ #End Region +#Region "NeoForge 列表" + + ''' + ''' 获取 NeoForge 的加载异常信息。若正常则返回 Nothing。 + ''' + Private Function LoadNeoForgeGetError() As String + If Not SelectedMinecraftId.StartsWith("1.") Then Return "没有可用版本" + If Not LoadNeoForge.State.IsLoader Then Return "正在获取版本列表……" + Dim Loader As LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)) = LoadNeoForge.State + If SelectedMinecraftId <> Loader.Input Then Return "正在获取版本列表……" + If Loader.State = LoadState.Loading Then Return "正在获取版本列表……" + If Loader.State = LoadState.Failed Then + Dim ErrorMessage As String = Loader.Error.Message + If ErrorMessage.Contains("没有可用版本") Then + Return "没有可用版本" + Else + Return "获取版本列表失败:" & ErrorMessage + End If + End If + If Loader.State <> LoadState.Finished Then Return "获取版本列表失败:未知错误,状态为 " & GetStringFromEnum(Loader.State) + For Each Version In Loader.Output + If Version.Category = "universal" OrElse Version.Category = "client" Then Continue For '跳过无法自动安装的版本 + If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容" + Return Nothing + Next + Return False + End Function + + '限制展开 + Private Sub CardNeoForge_PreviewSwap(sender As Object, e As RouteEventArgs) Handles CardNeoForge.PreviewSwap + If LoadNeoForgeGetError() IsNot Nothing Then e.Handled = True + End Sub + + ''' + ''' 尝试重新可视化 NeoForge 版本列表。 + ''' + Private Sub NeoForge_Loaded() Handles LoadForge.StateChanged + Try + If Not LoadNeoForge.State.IsLoader Then Exit Sub + Dim Loader As LoaderTask(Of String, List(Of DlNeoForgeVersionEntry)) = LoadNeoForge.State + If SelectedMinecraftId <> Loader.Input Then Exit Sub + If Loader.State <> LoadState.Finished Then Exit Sub + '获取要显示的版本 + Dim Versions As New List(Of DlNeoForgeVersionEntry) + Versions.AddRange(Loader.Output) '复制数组,以免 Output 在实例化后变空 + If Not Loader.Output.Any() Then Exit Sub + PanNeoForge.Children.Clear() + + 'Versions = Sort(Versions, Function(a, b) New Version(a.Version) > New Version(b.Version)).ToList + NeoForgeDownloadListItemPreload(PanNeoForge, Versions, AddressOf NeoForge_Selected, False) + For Each Version In Versions + PanNeoForge.Children.Add(NeoForgeDownloadListItem(Version, AddressOf NeoForge_Selected, False)) + Next + Catch ex As Exception + Log(ex, "可视化 NeoForge 安装版本列表出错", LogLevel.Feedback) + End Try + End Sub + + '选择与清除 + Private Sub NeoForge_Selected(sender As MyListItem, e As EventArgs) + SelectedNeoForge = sender.Tag + CardNeoForge.IsSwaped = True + OptiFine_Loaded() + SelectReload() + End Sub + Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp + SelectedNeoForge = Nothing + CardNeoForge.IsSwaped = True + e.Handled = True + OptiFine_Loaded() + SelectReload() + End Sub + +#End Region + #Region "Fabric 列表" '''