This commit is contained in:
LTCat
2026-08-06 22:30:13 +08:00
parent 7fb6e5d68e
commit 0ce35fe7e5
17 changed files with 167 additions and 151 deletions
+1 -1
View File
@@ -68,7 +68,7 @@ public static class Versions {
/// <summary>
/// 版本管理中 Mod 信息缓存的版本号。
/// </summary>
public const int LocalModCacheVersion = 18;
public const int LocalModCacheVersion = 19;
/// <summary>
/// Minecraft 本地实例信息缓存的版本号。
/// </summary>
+1 -1
View File
@@ -204,7 +204,7 @@ public static class JavaUtils {
static bool HasReparsePoint(FileSystemInfo info) {
if (PathUtils.IsParentOf(Paths.AppData + @".minecraft\runtime\", info.FullName)) return false; // 不筛查 PCL 的下载路径(#8928)
do {
if (info.Attributes.HasFlag(FileAttributes.ReparsePoint)) return true;
if (info.Attributes.HasFlagF(FileAttributes.ReparsePoint)) return true;
info = info is FileInfo fileInfo ? fileInfo.Directory : ((DirectoryInfo) info).Parent;
} while (info is not null);
return false;
+14 -4
View File
@@ -11,11 +11,11 @@ Public Class FormMain
Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
'统计更新日志条目
If BuildType = BuildTypes.Release Then
If LastVersion < 408 Then 'Release 2.13.1.0
If LastVersion < 408 Then 'Release 2.13.1.1
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:版本设置中,添加打开截图文件夹的快捷方式"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:导出整合包时,可以勾选导出光影设置文件"))
FeatureCount += 21
BugCount += 22
FeatureCount += 23
BugCount += 28
End If
If LastVersion < 406 Then 'Release 2.13.0.1
FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:重做 Java 管理与相关设置,允许调整 Java 优先级、指定 Java 版本范围等"))
@@ -93,6 +93,11 @@ Public Class FormMain
'3:BUG+ IMP* FEAT-
'2:BUG* IMP-
'1:BUG-
If LastVersion < 410 Then 'Snapshot 2.13.1.1
If LastVersion = 409 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:更新 Mod 时可能更新成另一个 Mod 加载器的版本"))
FeatureCount += 2
BugCount += 6
End If
If LastVersion < 409 Then 'Snapshot 2.13.1.0
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:版本设置中,添加打开截图文件夹的快捷方式"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:导出整合包时,可以勾选导出光影设置文件"))
@@ -781,7 +786,7 @@ Public Class FormMain
If PanMsg.Children.Any Then
If e.Key = Key.Enter Then
CType(PanMsg.Children(0), Object).Btn1_Click()
ElseIf e.Key = Key.Escape Then
ElseIf e.Key = Key.Escape OrElse (e.Key = Key.W AndAlso Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) Then
Dim Msg As Object = PanMsg.Children(0)
If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then
Msg.Btn3_Click()
@@ -823,6 +828,11 @@ Public Class FormMain
If TypeOf PageRight Is IRefreshable Then CType(PageRight, IRefreshable).Refresh()
Return
End If
'Ctrl + w 关闭程序
If e.Key = Key.W AndAlso Keyboard.Modifiers.HasFlag(ModifierKeys.Control) Then
EndProgram(True)
Return
End If
'调用启动游戏
If e.Key = Key.Enter AndAlso PageCurrent = FormMain.PageType.Launch Then
If IsAprilEnabled AndAlso Not IsAprilGiveup Then
@@ -6,12 +6,12 @@ Public Module ModBase
#Region "声明"
'下列版本信息由更新器自动修改
Public Const VersionBaseName As String = "2.13.1.0" '显示用版本名
Public Const VersionBaseName As String = "2.13.1.1" '显示用版本名
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
#If RELEASE Then
Public Const VersionCode As Integer = 408 '正式版
#Else
Public Const VersionCode As Integer = 409 '快照版
Public Const VersionCode As Integer = 410 '快照版
#End If
'版本信息
+8 -84
View File
@@ -217,8 +217,7 @@ Retry:
Optional Content As Object = Nothing, Optional Headers As Dictionary(Of String, String) = Nothing,
Optional SimulateBrowserHeaders As Boolean = False, Optional Timeout As Integer = 25000, Optional Encoding As Encoding = Nothing) As Byte()
If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求")
Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing,
Response As HttpResponseMessage = Nothing, HostIp As String = Nothing
Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing, Response As HttpResponseMessage = Nothing
Try
'构建 RequestClient
Directory.CreateDirectory(PathTemp & "Cache\Http\")
@@ -255,8 +254,6 @@ Retry:
Retrier.Attempt(delay:=Function() TimeSpan.FromMilliseconds(200), isRetryAllowed:=Function(ex) TypeOf ex Is FileNotFoundException OrElse TypeOf ex Is IOException, action:=
Sub(Attempt)
CancelToken?.Dispose() : CancelToken = New CancellationTokenSource(Timeout)
HostIp = DNSLookup(Request, CancelToken.Token)
If HostIp IsNot Nothing Then IPReliability.GetOrAdd(HostIp, -0.01) '预先降低一点,这样快速的重复请求会使用不同的 IP 以提高成功率
Dim ClonedRequest As HttpRequestMessage = Request.Clone() 'HttpRequestMessage 只能发送一次,重试时需要克隆一个新的
Try
Response = RequestClient.SendAsync(ClonedRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
@@ -274,26 +271,22 @@ Retry:
End Using
End Using
If Not Response.IsSuccessStatusCode Then
RecordIPReliability(HostIp, -0.7)
Dim ResponseMessage = ResponseBytes.GetString(Encoding)
Throw New HttpRequestCodeException(
$"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url},{HostIp}" &
$"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url}" &
If(String.IsNullOrEmpty(ResponseMessage), "", vbCrLf & ResponseMessage), Response.StatusCode, ResponseMessage)
End If
'输出
RecordIPReliability(HostIp, 0.5)
Return ResponseBytes
Catch ex As HttpRequestException
Throw
Catch ex As TimeoutException
Throw New WebException($"连接服务器超时,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url},IP:{HostIp})", WebExceptionStatus.Timeout)
Throw New WebException($"连接服务器超时,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url})", WebExceptionStatus.Timeout)
Catch ex As Exception
If TypeOf ex IsNot TaskCanceledException Then ex.ThrowIfCanceled() 'GetResultWithTimeout 会抛出 TaskCanceledException
RecordIPReliability(HostIp, -1)
If ex.IsBadNetwork Then
Throw New WebException($"网络请求失败,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url},IP:{HostIp})", WebExceptionStatus.Timeout)
Throw New WebException($"网络请求失败,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url})", WebExceptionStatus.Timeout)
Else
Throw New Exception($"网络请求出现意外异常({Method}, {Url},{HostIp})", ex)
Throw New Exception($"网络请求出现意外异常({Method}, {Url})", ex)
End If
Finally
Request?.Dispose()
@@ -304,72 +297,6 @@ Retry:
Private RequestClient As HttpClient = Nothing
Private RequestClientLock As New Object
''' <summary>
''' 进行 DNS 解析。它仅在选择的 IP 与系统默认的不一致时才对 URL 中的 Host 进行替换。
''' 返回请求时应使用的 IP。若解析失败,则返回 Nothing。
''' </summary>
Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationToken) As String
Dim Host = Request.RequestUri.Host
'不对部分有严格 SNI 限制的域名进行 DNS 解析(#8295)
If {"mojang.com", "minecraft.net", "minecraftservices.com"}.Any(Function(h) Host.ContainsIgnoreCase(h)) Then Return Nothing
'若一分钟内已失败过,则不再重复尝试解析,以减少断网时的 Exception 数量
Dim LastFailureTime As Date
If DNSFailureRecord.TryGetValue(Host, LastFailureTime) Then
If Date.Now - LastFailureTime < New TimeSpan(0, 1, 0) Then
Return Nothing
Else
DNSFailureRecord.Remove(Host)
End If
End If
'初步 DNS 解析
Dim Candidates As IPAddress()
Try
Dim AsyncResult = Dns.BeginGetHostAddresses(Host, Nothing, Nothing)
If WaitHandle.WaitAny(New WaitHandle() {AsyncResult.AsyncWaitHandle, CancelToken.WaitHandle}) Then Throw New TimeoutException("DNS 解析超时")
Candidates = Dns.EndGetHostAddresses(AsyncResult).Distinct.ToArray
Catch ex As Exception
Logger.Warn(ex, $"DNS 解析失败({Host})")
DNSFailureRecord(Host) = Date.Now
Return Nothing
End Try
If Not Candidates.Any Then
Logger.Info($"DNS 解析无结果({Host})")
Return Nothing
End If
'若同时存在 IPv4 和 IPv6 地址,仅选择其中一类(因为 GFW 可能只屏蔽了 IPv4 或 IPv6)
Dim Reliabilities = Candidates.ToDictionary(Function(i) i, Function(i) IPReliability.GetOrDefault(i.ToString, 0))
Dim IPv4Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetwork).ToArray
Dim IPv6Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetworkV6).ToArray
If IPv4Targets.Any AndAlso IPv6Targets.Any Then
Dim IPv4Reliability = IPv4Targets.Max(Function(i) Reliabilities(i))
Dim IPv6Reliability = IPv6Targets.Max(Function(i) Reliabilities(i))
If Host = "api.modrinth.com" Then IPv6Reliability -= 0.1 '让 Modrinth 优先使用 IPv4 地址(#6887)
Logger.Trace(Function() $"DNS IPv4/IPv6 选择({Host}),IPv4 {IPv4Reliability:0.000},IPv6 {IPv6Reliability:0.000}")
Candidates = If(IPv4Reliability >= IPv6Reliability, IPv4Targets, IPv6Targets)
End If
'选择可靠度最高的 IP
Dim Target As IPAddress = Candidates.MaxBy(Function(i) Reliabilities(i))
Request.Headers.Host = Request.RequestUri.Authority
Request.RequestUri = (New UriBuilder(Request.RequestUri) With {.Host = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString)}).Uri
Return Target.ToString
End Function
Private DNSFailureRecord As New ConcurrentDictionary(Of String, Date)
''' <summary>
''' 记录每个 IP 地址的请求可靠度。
''' 通常取值 -1 ~ +0.5,越高越好。未尝试过的 IP 应视为 0。
''' </summary>
Private IPReliability As New ConcurrentDictionary(Of String, Double)
''' <summary>
''' 根据请求结果,记录 IP 地址的可靠度。
''' </summary>
Private Sub RecordIPReliability(IP As String, Result As Double)
If IP Is Nothing Then Return
IPReliability.AddOrUpdate(IP,
Function() Result * 0.5,
Function(k, v) v * 0.5 + Result * 0.5
)
End Sub
#End Region
#Region "多线程下载引擎"
@@ -976,7 +903,7 @@ StartThread:
Logger.Log($"{LocalName}:开始,起始点 {Th.DownloadStart},{Th.Source.Url}", If(Th.DownloadStart = 0, LogLevel.Info, LogLevel.Trace))
Dim ResultStream As Stream = Nothing, HttpRequest As HttpRequestMessage = Nothing,
Response As HttpResponseMessage = Nothing, ResponseStream As Stream = Nothing,
CancelToken As CancellationTokenSource = Nothing, HostIp As String = Nothing
CancelToken As CancellationTokenSource = Nothing
'部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟
'延迟过长会导致某些特别慢的链接迟迟不被掐死
Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 15000) * (1 + Th.Source.FailCount), 30000)
@@ -987,7 +914,6 @@ StartThread:
HttpRequest = New HttpRequestMessage(HttpMethod.Get, Th.Source.Url)
SecretHeadersSign(Th.Source.Url, HttpRequest, SimulateBrowserHeaders)
CancelToken = New CancellationTokenSource(Timeout)
HostIp = DNSLookup(HttpRequest, CancelToken.Token) 'DNS 预解析
If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New Headers.RangeHeaderValue(Th.DownloadStart, Nothing)
Dim ContentLength As Long = 0
Response = ThreadClient.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
@@ -1119,15 +1045,13 @@ SourceBreak:
'本线程完成
Th.State = NetState.Finished
Logger.Trace(Function() $"{LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})")
RecordIPReliability(HostIp, 0.5)
End If
Catch ex As Exception
If ex.IsBadNetwork OrElse TypeOf ex Is TaskCanceledException Then
Logger.Info(New TimeoutException($"{LocalName}:超时({Timeout}ms),{ex.Message},IP:{If(String.IsNullOrEmpty(HostIp), "自动", "")}"))
Logger.Info(New TimeoutException($"{LocalName}:超时({Timeout}ms),{ex.Message}"))
Else
Logger.Info(ex, $"{LocalName}:出错,IP:{HostIp}")
Logger.Info(ex, $"{LocalName}:出错")
End If
RecordIPReliability(HostIp, -0.7)
SourceFail(Th, ex, False)
Finally
'释放资源
@@ -1379,11 +1379,16 @@ OnLoaded:
If Instance.State <> McInstanceState.Error AndAlso
ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionVanillaName", "Unknown") <> "Unknown" Then
'读取版本信息
Dim ReadCachedInfo =
Function(Key As String) As String
Dim Value = ReadIni(Instance.PathVersion & "PCL\Setup.ini", Key, Nothing)
Return If(Value = "", Nothing, Value) '对老版本 Setup.ini 的空值兼容
End Function
Dim VersionInfo As New McVersion With {
.Fabric = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionFabric", Nothing),
.Forge = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionForge", Nothing),
.NeoForge = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionNeoForge", Nothing),
.OptiFine = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionOptiFine", Nothing),
.Fabric = ReadCachedInfo("VersionFabric"),
.Forge = ReadCachedInfo("VersionForge"),
.NeoForge = ReadCachedInfo("VersionNeoForge"),
.OptiFine = ReadCachedInfo("VersionOptiFine"),
.HasLiteLoader = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionLiteLoader", False),
.VanillaName = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionVanillaName", "Unknown")
}
@@ -436,8 +436,8 @@ Public Module ModModpack
Urls = ResourceVersion.ParseModrinthTrackArguments(Urls,
ResourceVersion.DownloadReason.ModPack, TargetVersion.VanillaName, TargetVersion.ModLoaders).ToList 'Modrinth 来源追踪信息
Urls = Urls.Distinct.ToList()
Dim TargetPath As String = $"{McFolderSelected}versions\{InstanceName}\{File("path")}"
If Not Path.GetFullPath(TargetPath).StartsWithF(Path.GetFullPath($"{McFolderSelected}versions\{InstanceName}\")) Then
Dim TargetPath As String = Path.Combine(McFolderSelected, "versions", InstanceName, File("path").ToString().TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
If Not PathUtils.IsParentOf($"{McFolderSelected}versions\{InstanceName}\", TargetPath) Then
MyMsgBox($"整合包的文件路径超出了版本文件夹,请向整合包作者反馈此问题!{vbCrLf}目标:{Path.GetFullPath(TargetPath)}{vbCrLf}错误的文件:{TargetPath}", "文件路径校验失败", IsWarn:=True)
Throw New OperationCanceledException
End If
@@ -120,12 +120,12 @@ Public Module LocalResourceLoaders
Next
Logger.Info($"有 {Mods.Where(Function(m) m.Project Is Nothing).Count} 个 Mod 需要联网获取信息,{Mods.Where(Function(m) m.Project IsNot Nothing).Count} 个 Mod 需要更新信息")
If Not Mods.Any Then Return
'获取作为检查目标的加载器和版本
'获取作为检查目标的版本
'此处不应向下扩展检查的 MC 小版本,例如 Mod 在更新 1.16.5 后,对早期的 1.16.2 版本发布了修补补丁,这会导致 PCL 将 1.16.5 版本的 Mod 降级到 1.16.2
Dim TargetMcVersion As McVersion = Loader.Input.Target
Dim VanillaVersion = TargetMcVersion.VanillaName
'开始网络获取
Logger.Info($"目标加载器:{ModLoaders},版本:{VanillaVersion}")
Logger.Info($"正在检查 Mod 信息的版本:{VanillaVersion},加载器 {ModLoaders}")
Dim EndedThreadCount As Integer = 0, IsFailed As Boolean = False
Dim CurrentTaskThread As Thread = Thread.CurrentThread
'从 Modrinth 获取信息
@@ -140,6 +140,7 @@ Public Module LocalResourceLoaders
'步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
If ModrinthVersion.Count = 0 Then Return
Dim ModrinthMapping As New Dictionary(Of String, List(Of LocalResourceFile))
Dim ModrinthLoaders As New Dictionary(Of LocalResourceFile, List(Of String))
For Each Entry In Mods
If Not ModrinthVersion.ContainsKey(Entry.ModrinthHash) Then Continue For
If ModrinthVersion(Entry.ModrinthHash)("files")(0)("hashes")("sha1") <> Entry.ModrinthHash Then Continue For
@@ -149,6 +150,8 @@ Public Module LocalResourceLoaders
ModrinthMapping(ProjectId).Add(Entry)
'记录对应的 ProjectVersion
Dim File = ResourceVersion.FromPlatformJson(ModrinthVersion(Entry.ModrinthHash), ResourceTypes.Mod)
ModrinthLoaders(Entry) = ModrinthVersion(Entry.ModrinthHash)("loaders").
Select(Function(l) l.ToString.Lower).Distinct.OrderBy(Function(l) l).ToList
If Entry.ProjectVersion Is Nothing OrElse Entry.ProjectVersion.ReleaseDate < File.ReleaseDate Then
Entry.ProjectVersion = File
Else
@@ -169,17 +172,20 @@ Public Module LocalResourceLoaders
Next
Logger.Info($"已从 Modrinth 获取本地 Mod 信息,继续获取更新信息")
'步骤 4:获取更新信息
If ModLoaders = ModLoaders.None Then
Logger.Warn("该 Minecraft 版本没有可用的 Mod 加载器,不获取 Mod 更新信息")
Return
End If
Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update", HttpMethod.Post,
$"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"",
""loaders"": [""{ModLoaders.Flags.Join(""",""").Lower}""],""game_versions"": [""{VanillaVersion}""]}}", "application/json")
Dim ModsToUpdate = ModrinthMapping.SelectMany(Function(l) l.Value).ToList
If Not ModsToUpdate.Any Then Return
Dim UpdateData As String = ModsToUpdate.
Select(Function(m) $"{{""hash"": ""{m.ModrinthHash}"", ""loaders"": [""{ModrinthLoaders(m).Join(""",""")}""], ""game_versions"": [""{VanillaVersion}""]}}").
Join(",")
Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update_individual", HttpMethod.Post,
$"{{""algorithm"": ""sha1"", ""hashes"": [{UpdateData}]}}", "application/json")
For Each Entry In Mods
If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.ProjectVersion Is Nothing Then Continue For
Dim UpdateFile = ResourceVersion.FromPlatformJson(ModrinthUpdate(Entry.ModrinthHash), ResourceTypes.Mod)
If Not UpdateFile.DownloadAvailable Then Continue For
Dim UpdateLoaders = ModrinthUpdate(Entry.ModrinthHash)("loaders").
Select(Function(l) l.ToString.Lower).Distinct.OrderBy(Function(l) l).ToList
If Not UpdateLoaders.SequenceEqual(ModrinthLoaders(Entry)) Then Continue For
Logger.Trace(Function() $"本地文件 {Entry.ProjectVersion.FileName} 在 Modrinth 上的最新版为 {UpdateFile.FileName}")
If Entry.ProjectVersion.ReleaseDate >= UpdateFile.ReleaseDate OrElse Entry.ProjectVersion.Hash = UpdateFile.Hash Then Continue For
'设置更新日志与更新文件
@@ -217,6 +223,7 @@ Public Module LocalResourceLoaders
'步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
Dim ProjectIdToLocalFiles As New Dictionary(Of Integer, List(Of LocalResourceFile))
Dim CurseForgeLoaders As New Dictionary(Of LocalResourceFile, ModLoaders)
For Each Project In CurseForgeRaw
Dim ProjectId = Project("id").ToString
Dim Hash As UInteger = Project("file")("fileFingerprint")
@@ -226,6 +233,7 @@ Public Module LocalResourceLoaders
ProjectIdToLocalFiles.AddIntoValueCollection(ProjectId, Entry)
'记录对应的 ProjectVersion
Dim File = ResourceVersion.FromPlatformJson(Project("file"), ResourceTypes.Mod)
CurseForgeLoaders(Entry) = File.ModLoaders
If Entry.ProjectVersion Is Nothing OrElse Entry.ProjectVersion.ReleaseDate < File.ReleaseDate Then Entry.ProjectVersion = File
Next
Next
@@ -242,24 +250,23 @@ Public Module LocalResourceLoaders
If ProjectJson("isAvailable") IsNot Nothing AndAlso Not ProjectJson("isAvailable").ToObject(Of Boolean) Then Continue For
'设置 Entry 中的工程信息
Dim Project As New ResourceProject(ProjectJson)
Dim TargetLoaders As ModLoaders = ModLoaders.None
For Each LocalFile In ProjectIdToLocalFiles(Project.Id) '倒查防止 CurseForge 返回的内容有漏
If LocalFile.Project?.Platform = ResourcePlatforms.Modrinth Then
LocalFile.Project = LocalFile.Project '再次触发修改事件
Else
LocalFile.Project = Project
TargetLoaders = TargetLoaders Or LocalFile.Project.ModLoaders
End If
Next
TargetLoaders = TargetLoaders And ModLoaders '与目标 MC 版本的加载器取交集
'查找或许版本更新的文件列表
If ModLoaders = ModLoaders.None Then Continue For '无法判断 MC 版本使用的加载器,跳过更新检查
'查找可能有版本更新的文件列表
Dim LatestFiles = ProjectJson("latestFilesIndexes").Where(
Function(Entry)
'Mod Loader 匹配
If Entry("modLoader") Is Nothing Then Return False
Dim EntryLoader As ModLoaders = Resource.FromCurseForgeModLoaderType(Entry("modLoader").ToObject(Of Integer))
If Not TargetLoaders.HasFlag(EntryLoader) Then Return False
Dim EntryLoader As ModLoaders = If(Entry("modLoader") Is Nothing, ModLoaders.None,
Resource.FromCurseForgeModLoaderType(Entry("modLoader").ToObject(Of Integer)))
If CurseForgeLoaders(LocalFile) = ModLoaders.None Then
If EntryLoader <> ModLoaders.None Then Return False
ElseIf Not CurseForgeLoaders(LocalFile).HasFlagF(EntryLoader) Then
Return False
End If
'MC 版本匹配
Dim EntryVersion As String = Entry("gameVersion")
Return EntryVersion IsNot Nothing AndAlso EntryVersion = VanillaVersion
@@ -268,10 +275,11 @@ Public Module LocalResourceLoaders
For Each Entry In LatestFiles
Dim FileId = Entry("fileId").ToObject(Of Integer)
If Not UpdateFileIds.ContainsKey(FileId) Then UpdateFileIds(FileId) = New List(Of LocalResourceFile)
UpdateFileIds(FileId).AddRange(ProjectIdToLocalFiles(Project.Id))
UpdateFileIds(FileId).Add(LocalFile)
FileIdToProjectSlug(FileId) = Project.Slug
Next
Next
Next
Logger.Info($"已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息")
If Not UpdateFileIds.Any() Then Return
@@ -283,6 +291,10 @@ Public Module LocalResourceLoaders
Dim File = ResourceVersion.FromPlatformJson(FileJson, ResourceTypes.Mod)
If Not File.DownloadAvailable Then Continue For
For Each Entry As LocalResourceFile In UpdateFileIds(File.Id)
If File.ModLoaders <> CurseForgeLoaders(Entry) Then
Logger.Warn($"从 CurseForge 获取到的更新文件 {File.FileName} 加载器为 {File.ModLoaders},与本地文件 {Entry.File.Name} 的加载器 {CurseForgeLoaders(Entry)} 不一致,已忽略", LogBehavior.None)
Continue For
End If
If UpdateFiles.ContainsKey(Entry) AndAlso UpdateFiles(Entry).ReleaseDate >= File.ReleaseDate Then Continue For
UpdateFiles(Entry) = File
Next
@@ -87,7 +87,7 @@ Public Class ResourceProject
Get
If Not LoadedDatabase Then
LoadedDatabase = True
If Types.HasFlag(ResourceTypes.Mod) OrElse Types.HasFlag(ResourceTypes.DataPack) Then
If Types.HasFlagF(ResourceTypes.Mod) OrElse Types.HasFlagF(ResourceTypes.DataPack) Then
_WikiEntry = WikiEntry.All.Value.FirstOrDefault(Function(c) c.Slugs.GetOrDefault(Platform) = Slug)
End If
End If
@@ -415,7 +415,7 @@ Public Class ResourceProject
If Not MissingProjectIds.Any() Then Return
'联网获取剩余的工程对象
Dim Projects As JArray
If Platform.HasFlag(ResourcePlatforms.CurseForge) Then
If Platform.HasFlagF(ResourcePlatforms.CurseForge) Then
Projects = DlModRequest("https://api.curseforge.com/v1/mods",
HttpMethod.Post, "{""modIds"": [" & MissingProjectIds.Join(","c) & "]}", "application/json")("data")
Else
@@ -21,8 +21,8 @@ Public Class ResourceSearcher
''' </summary>
Public ReadOnly Property CanContinue As Boolean
Get
If Tag.StartsWithF("/") OrElse Not Sources.HasFlag(ResourcePlatforms.CurseForge) Then Storage.CurseForgeTotal = 0
If Tag.EndsWithF("/") OrElse Not Sources.HasFlag(ResourcePlatforms.Modrinth) Then Storage.ModrinthTotal = 0
If Tag.StartsWithF("/") OrElse Not Sources.HasFlagF(ResourcePlatforms.CurseForge) Then Storage.CurseForgeTotal = 0
If Tag.EndsWithF("/") OrElse Not Sources.HasFlagF(ResourcePlatforms.Modrinth) Then Storage.ModrinthTotal = 0
If Storage.CurseForgeTotal = -1 OrElse Storage.ModrinthTotal = -1 Then Return True
Return Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal
End Get
@@ -241,7 +241,7 @@ Public Class ResourceSearcher
Next
End Function
'CurseForge
If Request.Sources.HasFlag(ResourcePlatforms.CurseForge) Then
If Request.Sources.HasFlagF(ResourcePlatforms.CurseForge) Then
'数据库搜索
Static CurseForgeSearchEntries As List(Of SearchEntry(Of WikiEntry)) = GetSearchEntries(ResourcePlatforms.CurseForge).ToList()
Dim CurseForgeSearchResults = Search(CurseForgeSearchEntries, RawSearchText, 100, 0.25)
@@ -258,7 +258,7 @@ Public Class ResourceSearcher
End If
End If
'Modrinth
If Request.Sources.HasFlag(ResourcePlatforms.Modrinth) Then
If Request.Sources.HasFlagF(ResourcePlatforms.Modrinth) Then
'数据库搜索
Static ModrinthSearchEntries As List(Of SearchEntry(Of WikiEntry)) = GetSearchEntries(ResourcePlatforms.Modrinth).ToList()
Dim ModrinthSearchResults = Search(ModrinthSearchEntries, RawSearchText, 100, 0.25)
@@ -303,7 +303,7 @@ NextPage:
Try
'CurseForge 搜索
If Request.Sources.HasFlag(ResourcePlatforms.CurseForge) AndAlso
If Request.Sources.HasFlagF(ResourcePlatforms.CurseForge) AndAlso
Not (Storage.CurseForgeTotal > -1 AndAlso Storage.CurseForgeTotal <= Storage.CurseForgeOffset) AndAlso '剩余的未显示的搜索结果不足
(Not IsChineseSearch OrElse (IsChineseSearch AndAlso Not String.IsNullOrEmpty(CurseForgeAltSearchText))) Then '如果是中文搜索,就只在有对应搜索关键词的时候才继续
WorkThreads.Add(RunInNewThread(
@@ -317,7 +317,7 @@ NextPage:
For Each JsonEntry As JObject In RequestResult("data")
Dim Project As New ResourceProject(JsonEntry)
If Request.Type = ResourceTypes.ResourcePack AndAlso Project.Tags.Contains("数据包") Then Continue For 'CurseForge 将一些数据包分类成了资源包
If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlag(Request.Type) Then Continue For '过滤分区不匹配的搜索结果(#8265)
If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlagF(Request.Type) Then Continue For '过滤分区不匹配的搜索结果(#8265)
ProjectList.Add(Project)
Next
'更新结果
@@ -335,7 +335,7 @@ NextPage:
End If
'Modrinth 搜索
If Request.Sources.HasFlag(ResourcePlatforms.Modrinth) AndAlso
If Request.Sources.HasFlagF(ResourcePlatforms.Modrinth) AndAlso
Not (Storage.ModrinthTotal > -1 AndAlso Storage.ModrinthTotal <= Storage.ModrinthOffset) AndAlso '剩余的未显示的搜索结果不足
(Not IsChineseSearch OrElse (IsChineseSearch AndAlso Not String.IsNullOrEmpty(ModrinthAltSearchText))) Then '如果是中文搜索,就只在有对应搜索关键词的时候才继续
WorkThreads.Add(RunInNewThread(
@@ -363,7 +363,7 @@ NextPage:
End If
'Modrinth 直接获取工程
If Request.Sources.HasFlag(ResourcePlatforms.Modrinth) AndAlso
If Request.Sources.HasFlagF(ResourcePlatforms.Modrinth) AndAlso
Not (Storage.ModrinthTotal > -1 AndAlso Storage.ModrinthTotal <= Storage.ModrinthOffset) AndAlso '剩余的未显示的搜索结果不足
ModrinthSlugs.Any Then '有直接获取的 Slug
WorkThreads.Add(RunInNewThread(
@@ -375,7 +375,7 @@ NextPage:
For Each JsonEntry As JObject In DlModRequest(ModrinthUrl)
Dim Project As New ResourceProject(JsonEntry)
'应用筛选
If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlag(Request.Type) Then Continue For
If Request.Type <> ResourceTypes.Any AndAlso Not Project.Types.HasFlagF(Request.Type) Then Continue For
If Not String.IsNullOrEmpty(Request.Tag) AndAlso
Not JsonEntry("categories").Any(Function(c) c.ToString = Request.Tag.AfterLast("/")) Then Continue For 'Project.Tags 已经转换成中文了,只能从 json 判
If Request.ModLoaders <> ModLoaders.None AndAlso Not IgnoreModLoaderFilter AndAlso
@@ -405,7 +405,7 @@ NextPage:
'仅保留兼容 Forge 的 Mod,或老版本中没有标注任何 Mod Loader 的 Mod
If IgnoreModLoaderFilter Then
RawResults.KeepIf(Function(p) p.ModLoaders = ModLoaders.None OrElse p.ModLoaders.HasFlag(ModLoaders.Forge))
RawResults.KeepIf(Function(p) p.ModLoaders = ModLoaders.None OrElse p.ModLoaders.HasFlagF(ModLoaders.Forge))
End If
'确保存在结果
@@ -185,7 +185,7 @@ Public Class ResourceVersion
For Each Loader As ModLoaders In EnumUtils.GetAllFlags(Of ModLoaders)()
If RawLoaders.Contains(Loader.ToString.Lower) Then .ModLoaders = .ModLoaders Or Loader
Next
If .ResourceType.HasFlag(ResourceTypes.Mod) OrElse .ResourceType.HasFlag(ResourceTypes.DataPack) Then
If .ResourceType.HasFlagF(ResourceTypes.Mod) OrElse .ResourceType.HasFlagF(ResourceTypes.DataPack) Then
If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then .ResourceType = ResourceTypes.Plugin 'Veinminer Enchantment 同时支持服务端与 Fabric
If RawLoaders.Contains("datapack") Then .ResourceType = ResourceTypes.DataPack
If .ModLoaders.Flags().Any() Then .ResourceType = ResourceTypes.Mod
@@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices
' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
' 方法是按如下所示使用“*”
<Assembly: AssemblyVersion("2.13.1.0")>
<Assembly: AssemblyFileVersion("2.13.1.0")>
<Assembly: AssemblyVersion("2.13.1.1")>
<Assembly: AssemblyFileVersion("2.13.1.1")>
<Assembly: NeutralResourcesLanguage("")>
@@ -11,6 +11,7 @@ Public Module ModDownloadLib
Dim VersionFolder As String = McFolderSelected & "versions\" & InstanceName & "\"
Dim Loaders As New List(Of LoaderBase)
Dim NewInstance As McInstance = Nothing
'下载版本 Json 文件
If JsonUrl Is Nothing Then
@@ -23,14 +24,19 @@ Public Module ModDownloadLib
Loaders.Add(New LoaderDownload(McDownloadClientJsonName, New List(Of NetFile) From {
New NetFile(DlSourceLauncherOrMetaGet(If(JsonUrl, "")), VersionFolder & InstanceName & ".json", New FileChecker With {.IsJson = True})
}) With {.ProgressWeight = 3})
'读取 Json 文件并创建实例
Loaders.Add(New LoaderTask(Of String, String)("读取 json 文件",
Sub(Task As LoaderTask(Of String, String))
NewInstance = New McInstance(VersionFolder)
NewInstance.Load()
End Sub) With {.ProgressWeight = 0, .Show = False})
'下载支持库文件
Dim LoadersLib As New List(Of LoaderBase)
LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析原版支持库文件(副加载器)",
Sub(Task As LoaderTask(Of String, List(Of NetFile)))
Thread.Sleep(50) '等待 JSON 文件实际写入硬盘(#3710)
Logger.Info($"开始分析原版支持库文件:{VersionFolder}")
Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder))
Task.Output = McLibNetFilesFromInstance(NewInstance)
End Sub) With {.ProgressWeight = 1, .Show = False})
LoadersLib.Add(New LoaderDownload("下载原版支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 13, .Show = False})
Loaders.Add(New LoaderCombo(Of String)(McDownloadClientLibName, LoadersLib) With {.Block = False, .ProgressWeight = 14})
@@ -40,7 +46,7 @@ Public Module ModDownloadLib
LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析资源文件索引地址(副加载器)",
Sub(Task As LoaderTask(Of String, List(Of NetFile)))
Try
Dim AssetIndex = DlClientAssetIndexGet(New McInstance(VersionFolder))
Dim AssetIndex = DlClientAssetIndexGet(NewInstance)
Task.Output = If(AssetIndex Is Nothing, New List(Of NetFile), New List(Of NetFile) From {AssetIndex})
Catch ex As Exception
Throw New Exception("分析资源文件索引地址失败", ex)
@@ -56,7 +62,7 @@ Public Module ModDownloadLib
End Sub) With {.ProgressWeight = 1, .Show = False})
LoadersAssets.Add(New LoaderDownload("下载资源文件索引(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 3, .Show = False})
LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析所需资源文件(副加载器)",
Sub(Task) Task.Output = McAssetsFixList(New McInstance(VersionFolder), True, Task)) With {.ProgressWeight = 0.01, .Show = False})
Sub(Task) Task.Output = McAssetsFixList(NewInstance, True, Task)) With {.ProgressWeight = 0.01, .Show = False})
LoadersAssets.Add(New LoaderDownload("下载资源文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 14, .Show = False})
Loaders.Add(New LoaderCombo(Of String)("下载原版资源文件", LoadersAssets) With {.Block = False, .ProgressWeight = 18})
@@ -307,7 +307,7 @@ Public Class PageInstanceOverall
Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
Dim IsHintIndie As Boolean = PageInstanceLeft.Instance.State <> McInstanceState.Error AndAlso PageInstanceLeft.Instance.PathIndie <> McFolderSelected
Dim SavesFolder = DirectoryUtils.GetInfo(PageInstanceLeft.Instance.PathIndie & "saves\")
Dim SaveEntries = If(SavesFolder.Exists, SavesFolder.EnumerateDirectories.OrderByDescending(Function(Save) Save.LastWriteTime).Select(Function(Save) $"{Save.Name}(上次修改:{StringUtils.FormatTimeSpan(Save.LastWriteTime - Date.Now, False)})").ToList, New List(Of String))
Dim SaveEntries = If(IsHintIndie AndAlso SavesFolder.Exists, SavesFolder.EnumerateDirectories.OrderByDescending(Function(Save) Save.LastWriteTime).Select(Function(Save) $"{Save.Name}(上次修改:{StringUtils.FormatTimeSpan(Save.LastWriteTime - Date.Now, False)})").ToList, New List(Of String))
Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {PageInstanceLeft.Instance.Name} 吗?" &
If(IsHintIndie, vbCrLf & "该版本对应的存档、资源包、Mod 等文件也将被一并删除!" &
If(SaveEntries.Any, vbCrLf & vbCrLf & "这会删除以下存档:" & vbCrLf & "· " & SaveEntries.Join(vbCrLf & "· "), ""), ""),
@@ -134,8 +134,10 @@
<WrapPanel Margin="0,0,0,10"> <TextBlock Margin="0,1.5,6,0" Text="L" />
<TextBlock Margin="0,1.5,6,0" Text="Z" />
<TextBlock Margin="0,1.5,6,0" Text="N" />
<TextBlock Margin="0,1.5,6,0" Text="P." />
<TextBlock Margin="0,1.5,6,0" Text="...:" />
<TextBlock Margin="0,1.5,6,0" Text="妖" />
<TextBlock Margin="0,1.5,6,0" Text="FL" />
<TextBlock Margin="0,1.5,6,0" Text="萧" />
<TextBlock Margin="0,1.5,6,0" Text="乔" />
<TextBlock Margin="0,1.5,6,0" Text="cc" />
@@ -150,6 +152,7 @@
<TextBlock Margin="0,1.5,6,0" Text="33" />
<TextBlock Margin="0,1.5,6,0" Text="kk" />
<TextBlock Margin="0,1.5,6,0" Text="jjb" />
<TextBlock Margin="0,1.5,6,0" Text="狮." />
<TextBlock Margin="0,1.5,6,0" Text="+1" />
<TextBlock Margin="0,1.5,6,0" Text="Iirs" />
<TextBlock Margin="0,1.5,6,0" Text="Lio" />
@@ -158,16 +161,22 @@
<TextBlock Margin="0,1.5,6,0" Text="Sht" />
<TextBlock Margin="0,1.5,6,0" Text="Nul" />
<TextBlock Margin="0,1.5,6,0" Text="250" />
<TextBlock Margin="0,1.5,6,0" Text="Life" />
<TextBlock Margin="0,1.5,6,0" Text="kile" />
<TextBlock Margin="0,1.5,6,0" Text="pbc" />
<TextBlock Margin="0,1.5,6,0" Text="Aya" />
<TextBlock Margin="0,1.5,6,0" Text="mjp" />
<TextBlock Margin="0,1.5,6,0" Text="azzt" />
<TextBlock Margin="0,1.5,6,0" Text="小小" />
<TextBlock Margin="0,1.5,6,0" Text="冒子" />
<TextBlock Margin="0,1.5,6,0" Text="王道" />
<TextBlock Margin="0,1.5,6,0" Text="鸡人" />
<TextBlock Margin="0,1.5,6,0" Text="喵根" />
<TextBlock Margin="0,1.5,6,0" Text="折花" />
<TextBlock Margin="0,1.5,6,0" Text="归墟" />
<TextBlock Margin="0,1.5,6,0" Text="墨渊" />
<TextBlock Margin="0,1.5,6,0" Text="键来" />
<TextBlock Margin="0,1.5,6,0" Text="睚眦" />
<TextBlock Margin="0,1.5,6,0" Text="而后" />
<TextBlock Margin="0,1.5,6,0" Text="南安" />
<TextBlock Margin="0,1.5,6,0" Text="曰久" />
@@ -213,7 +222,21 @@
<TextBlock Margin="0,1.5,6,0" Text="哈哈" />
<TextBlock Margin="0,1.5,6,0" Text="小洛" />
<TextBlock Margin="0,1.5,6,0" Text="璃辰" />
<TextBlock Margin="0,1.5,6,0" Text="茉莉" />
<TextBlock Margin="0,1.5,6,0" Text="旅人" />
<TextBlock Margin="0,1.5,6,0" Text="航卡" />
<TextBlock Margin="0,1.5,6,0" Text="班总" />
<TextBlock Margin="0,1.5,6,0" Text="小圆" />
<TextBlock Margin="0,1.5,6,0" Text="梨子" />
<TextBlock Margin="0,1.5,6,0" Text="风?" />
<TextBlock Margin="0,1.5,6,0" Text="瓜瓜" />
<TextBlock Margin="0,1.5,6,0" Text="阳光" />
<TextBlock Margin="0,1.5,6,0" Text="菜菜" />
<TextBlock Margin="0,1.5,6,0" Text="副连" />
<TextBlock Margin="0,1.5,6,0" Text="如舟" />
<TextBlock Margin="0,1.5,6,0" Text="bow" />
<TextBlock Margin="0,1.5,6,0" Text="Byet" />
<TextBlock Margin="0,1.5,6,0" Text="Kian" />
<TextBlock Margin="0,1.5,6,0" Text="ZDD" />
<TextBlock Margin="0,1.5,6,0" Text="Um1" />
<TextBlock Margin="0,1.5,6,0" Text="2514" />
@@ -224,21 +247,26 @@
<TextBlock Margin="0,1.5,6,0" Text="joker" />
<TextBlock Margin="0,1.5,6,0" Text="RXZY" />
<TextBlock Margin="0,1.5,6,0" Text="白成y" />
<TextBlock Margin="0,1.5,6,0" Text="mww" />
<TextBlock Margin="0,1.5,6,0" Text="Rofni" />
<TextBlock Margin="0,1.5,6,0" Text="mzqc" />
<TextBlock Margin="0,1.5,6,0" Text="jason" />
<TextBlock Margin="0,1.5,6,0" Text="youzi" />
<TextBlock Margin="0,1.5,6,0" Text="xxlzta" />
<TextBlock Margin="0,1.5,6,0" Text="end_x" />
<TextBlock Margin="0,1.5,6,0" Text="QWYJ" />
<TextBlock Margin="0,1.5,6,0" Text="qwqw" />
<TextBlock Margin="0,1.5,6,0" Text="Angel" />
<TextBlock Margin="0,1.5,6,0" Text="15041" />
<TextBlock Margin="0,1.5,6,0" Text="晓空er" />
<TextBlock Margin="0,1.5,6,0" Text="kaoyu" />
<TextBlock Margin="0,1.5,6,0" Text="肥富帅" />
<TextBlock Margin="0,1.5,6,0" Text="小王子" />
<TextBlock Margin="0,1.5,6,0" Text="狐狸涩" />
<TextBlock Margin="0,1.5,6,0" Text="大涡流" />
<TextBlock Margin="0,1.5,6,0" Text="无名氏" />
<TextBlock Margin="0,1.5,6,0" Text="紫风狐" />
<TextBlock Margin="0,1.5,6,0" Text="杨巨焕" />
<TextBlock Margin="0,1.5,6,0" Text="范桂圆" />
<TextBlock Margin="0,1.5,6,0" Text="芸芸云" />
<TextBlock Margin="0,1.5,6,0" Text="快夏波" />
@@ -255,6 +283,7 @@
<TextBlock Margin="0,1.5,6,0" Text="乐永昶" />
<TextBlock Margin="0,1.5,6,0" Text="lingke" />
<TextBlock Margin="0,1.5,6,0" Text="东雪莲" />
<TextBlock Margin="0,1.5,6,0" Text="殷疯子" />
<TextBlock Margin="0,1.5,6,0" Text="伊文斯" />
<TextBlock Margin="0,1.5,6,0" Text="丁乙恒" />
<TextBlock Margin="0,1.5,6,0" Text="朔望月" />
@@ -273,12 +302,22 @@
<TextBlock Margin="0,1.5,6,0" Text="浮千傀" />
<TextBlock Margin="0,1.5,6,0" Text="恰啦啦" />
<TextBlock Margin="0,1.5,6,0" Text="梦痕丿" />
<TextBlock Margin="0,1.5,6,0" Text="三叶铭" />
<TextBlock Margin="0,1.5,6,0" Text="卿姬昉" />
<TextBlock Margin="0,1.5,6,0" Text="疯一回" />
<TextBlock Margin="0,1.5,6,0" Text="李困困" />
<TextBlock Margin="0,1.5,6,0" Text="厄尔萨" />
<TextBlock Margin="0,1.5,6,0" Text="伏拉夫" />
<TextBlock Margin="0,1.5,6,0" Text="α-蒎烯" />
<TextBlock Margin="0,1.5,6,0" Text="你的JK" />
<TextBlock Margin="0,1.5,6,0" Text="jiujinh" />
<TextBlock Margin="0,1.5,6,0" Text="慕qwq" />
<TextBlock Margin="0,1.5,6,0" Text="Twicat" />
<TextBlock Margin="0,1.5,6,0" Text="stenac" />
<TextBlock Margin="0,1.5,6,0" Text="JOKER" />
<TextBlock Margin="0,1.5,6,0" Text="半日谣." />
<TextBlock Margin="0,1.5,6,0" Text="EX星辞" />
<TextBlock Margin="0,1.5,6,0" Text="Tefrish" />
<TextBlock Margin="0,1.5,6,0" Text="Triple9" />
<TextBlock Margin="0,1.5,6,0" Text="gediao" />
<TextBlock Margin="0,1.5,6,0" Text="Zen1th" />
@@ -292,12 +331,18 @@
<TextBlock Margin="0,1.5,6,0" Text="Yi_xuan" />
<TextBlock Margin="0,1.5,6,0" Text="MC芬达" />
<TextBlock Margin="0,1.5,6,0" Text="shallow" />
<TextBlock Margin="0,1.5,6,0" Text="ETL蓝夢" />
<TextBlock Margin="0,1.5,6,0" Text="𝕳轩辕氏" />
<TextBlock Margin="0,1.5,6,0" Text="谜茶360" />
<TextBlock Margin="0,1.5,6,0" Text="颜沐Jun" />
<TextBlock Margin="0,1.5,6,0" Text="CS2七松" />
<TextBlock Margin="0,1.5,6,0" Text="Eventful" />
<TextBlock Margin="0,1.5,6,0" Text="Molioos" />
<TextBlock Margin="0,1.5,6,0" Text="logicum" />
<TextBlock Margin="0,1.5,6,0" Text="Mr_loba" />
<TextBlock Margin="0,1.5,6,0" Text="一叶知秋" />
<TextBlock Margin="0,1.5,6,0" Text="我妻狗卷" />
<TextBlock Margin="0,1.5,6,0" Text="御神凌霜" />
<TextBlock Margin="0,1.5,6,0" Text="狼烟居士" />
<TextBlock Margin="0,1.5,6,0" Text="千风伶羽" />
<TextBlock Margin="0,1.5,6,0" Text="mumiao" />
@@ -332,11 +377,16 @@
<TextBlock Margin="0,1.5,6,0" Text="春山如笑" />
<TextBlock Margin="0,1.5,6,0" Text="幻天阿绝" />
<TextBlock Margin="0,1.5,6,0" Text="忘道蜂枫" />
<TextBlock Margin="0,1.5,6,0" Text="深海无渊" />
<TextBlock Margin="0,1.5,6,0" Text="心软的神" />
<TextBlock Margin="0,1.5,6,0" Text="砂锅豆腐" />
<TextBlock Margin="0,1.5,6,0" Text="好人好逝" />
<TextBlock Margin="0,1.5,6,0" Text="登峰破晓" />
<TextBlock Margin="0,1.5,6,0" Text="东隅未殁" />
<TextBlock Margin="0,1.5,6,0" Text="星花银影" />
<TextBlock Margin="0,1.5,6,0" Text="往事如昔" />
<TextBlock Margin="0,1.5,6,0" Text="林克之空" />
<TextBlock Margin="0,1.5,6,0" Text="0X00001" />
<TextBlock Margin="0,1.5,6,0" Text="MuMAA" />
<TextBlock Margin="0,1.5,6,0" Text="溯枫qwq" />
<TextBlock Margin="0,1.5,6,0" Text="Tacenda" />
@@ -344,6 +394,7 @@
<TextBlock Margin="0,1.5,6,0" Text="👁👄👃" />
<TextBlock Margin="0,1.5,6,0" Text="秋慕owo" />
<TextBlock Margin="0,1.5,6,0" Text="船长Yuki" />
<TextBlock Margin="0,1.5,6,0" Text="HIMEKO" />
<TextBlock Margin="0,1.5,6,0" Text="是小61呀" />
<TextBlock Margin="0,1.5,6,0" Text="47号虾球" />
<TextBlock Margin="0,1.5,6,0" Text="Alancnel" />
@@ -360,6 +411,7 @@
<TextBlock Margin="0,1.5,6,0" Text="MselQAQ" />
<TextBlock Margin="0,1.5,6,0" Text="UZQueen" />
<TextBlock Margin="0,1.5,6,0" Text="hihospital" />
<TextBlock Margin="0,1.5,6,0" Text="cutegirl3k" />
<TextBlock Margin="0,1.5,6,0" Text="Prifo_lone" />
<TextBlock Margin="0,1.5,6,0" Text="有....暗星!" />
<TextBlock Margin="0,1.5,6,0" Text="Hitsser 1.0" />
@@ -382,11 +434,13 @@
<TextBlock Margin="0,1.5,6,0" Text="离人如飘萍" />
<TextBlock Margin="0,1.5,6,0" Text="秋葉逐殘夏" />
<TextBlock Margin="0,1.5,6,0" Text="水星有海吗" />
<TextBlock Margin="0,1.5,6,0" Text="吃饱爱睡觉" />
<TextBlock Margin="0,1.5,6,0" Text="ac纯萌新cy" />
<TextBlock Margin="0,1.5,6,0" Text="岁月suiyue" />
<TextBlock Margin="0,1.5,6,0" Text="陌然MajorC" />
<TextBlock Margin="0,1.5,6,0" Text="-Pareidolia." />
<TextBlock Margin="0,1.5,6,0" Text="蛙蛙大王Kzr" />
<TextBlock Margin="0,1.5,6,0" Text="Aurorawalk" />
<TextBlock Margin="0,1.5,6,0" Text="Yhcmdbfwz" />
<TextBlock Margin="0,1.5,6,0" Text="Ayers赜蠹嬭" />
<TextBlock Margin="0,1.5,6,0" Text="We1J1uSh1" />
@@ -405,6 +459,7 @@
<TextBlock Margin="0,1.5,6,0" Text="小田love白子" />
<TextBlock Margin="0,1.5,6,0" Text="Sasha_Xiong" />
<TextBlock Margin="0,1.5,6,0" Text="赤狸_crisnow" />
<TextBlock Margin="0,1.5,6,0" Text="dorod月gour" />
<TextBlock Margin="0,1.5,6,0" Text="Name0emaN" />
<TextBlock Margin="0,1.5,6,0" Text="happypickles" />
<TextBlock Margin="0,1.5,6,0" Text="无中生有为dd" />
@@ -439,13 +494,17 @@
<TextBlock Margin="0,1.5,6,0" Text="没什么名字好取" />
<TextBlock Margin="0,1.5,6,0" Text="Wanderer5147" />
<TextBlock Margin="0,1.5,6,0" Text="取个名字耗阳寿" />
<TextBlock Margin="0,1.5,6,0" Text="北熊何时与狼行" />
<TextBlock Margin="0,1.5,6,0" Text="向着有风的地方" />
<TextBlock Margin="0,1.5,6,0" Text="AAA神秘人批发" />
<TextBlock Margin="0,1.5,6,0" Text="Bangbang2025" />
<TextBlock Margin="0,1.5,6,0" Text="贝吉蒙Begimon" />
<TextBlock Margin="0,1.5,6,0" Text="取个名字真难0.0" />
<TextBlock Margin="0,1.5,6,0" Text="NoTuring_Steve" />
<TextBlock Margin="0,1.5,6,0" Text="VividerCafe2379" />
<TextBlock Margin="0,1.5,6,0" Text="冰冰watermelon" />
<TextBlock Margin="0,1.5,6,0" Text="DOODOODOOD" />
<TextBlock Margin="0,1.5,6,0" Text="怕做苦力的苦力怕" />
<TextBlock Margin="0,1.5,6,0" Text="小句号就是小句号" />
<TextBlock Margin="0,1.5,6,0" Text="弹跳爆炸小鱼珠🐟" />
<TextBlock Margin="0,1.5,6,0" Text="Xanadu Silk Fable" />
@@ -186,7 +186,7 @@ Public Class PageSelectRight
Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
Dim IsHintIndie As Boolean = Instance.State <> McInstanceState.Error AndAlso Instance.PathIndie <> McFolderSelected
Dim SavesFolder = DirectoryUtils.GetInfo(Instance.PathIndie & "saves\")
Dim SaveEntries = If(SavesFolder.Exists, SavesFolder.EnumerateDirectories.OrderByDescending(Function(Save) Save.LastWriteTime).Select(Function(Save) $"{Save.Name}(上次修改:{StringUtils.FormatTimeSpan(Save.LastWriteTime - Date.Now, False)})").ToList, New List(Of String))
Dim SaveEntries = If(IsHintIndie AndAlso SavesFolder.Exists, SavesFolder.EnumerateDirectories.OrderByDescending(Function(Save) Save.LastWriteTime).Select(Function(Save) $"{Save.Name}(上次修改:{StringUtils.FormatTimeSpan(Save.LastWriteTime - Date.Now, False)})").ToList, New List(Of String))
Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {Instance.Name} 吗?" &
If(IsHintIndie, vbCrLf & "该版本对应的存档、资源包、Mod 等文件也将被一并删除!", "") &
If(SaveEntries.Any, vbCrLf & vbCrLf & "这会删除以下存档:" & vbCrLf & "· " & SaveEntries.Join(vbCrLf & "· "), ""),