This commit is contained in:
龙腾猫跃
2026-03-08 20:56:13 +08:00
parent 2bffafcd7a
commit be67c4ccc2
27 changed files with 3090 additions and 1722 deletions
@@ -846,6 +846,11 @@ NoSubtitle:
''' </summary>
Public SearchText As String = Nothing
''' <summary>
''' 在进行中文搜索时,CurseForge 的替代搜索文本。
''' 由于 CurseForge API 在有任意关键词未匹配的时候就不显示结果,所以不能使用与 Modrinth 相同的算法。
''' </summary>
Public CurseForgeAltSearchText As String = Nothing
''' <summary>
''' 允许的来源。
''' </summary>
Public Source As CompSourceType = CompSourceType.Any
@@ -884,7 +889,7 @@ NoSubtitle:
If Tag <> "" Then Address += "&categoryId=" & Tag.BeforeFirst("/")
If ModLoader <> CompModLoaderType.Any Then Address += "&modLoaderType=" & CType(ModLoader, Integer)
If Not String.IsNullOrEmpty(GameVersion) Then Address += "&gameVersion=" & GameVersion
If Not String.IsNullOrEmpty(SearchText) Then Address += "&searchFilter=" & Net.WebUtility.UrlEncode(SearchText)
If Not String.IsNullOrEmpty(If(CurseForgeAltSearchText, SearchText)) Then Address += "&searchFilter=" & Net.WebUtility.UrlEncode(If(CurseForgeAltSearchText, SearchText))
If Storage.CurseForgeOffset > 0 Then Address += "&index=" & Storage.CurseForgeOffset
Return Address
End Function
@@ -995,62 +1000,86 @@ NoSubtitle:
'构造搜索请求
Dim SearchEntries As New List(Of SearchEntry(Of CompDatabaseEntry))
For Each Entry In CompDatabase
If Entry.ChineseName.Contains("动态的树") Then Continue For '这玩意儿附属太多了
SearchEntries.Add(New SearchEntry(Of CompDatabaseEntry) With {
.Item = Entry,
.SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
New KeyValuePair(Of String, Double)(Entry.ChineseName & If(Entry.CurseForgeSlug, "") & If(Entry.ModrinthSlug, ""), 1)
.SearchSource = New List(Of SearchSource) From {
New SearchSource(Entry.ChineseName.BeforeFirst(" (").Split({"/"c}, StringSplitOptions.RemoveEmptyEntries), 1), '部分 Mod 有别名
New SearchSource(Entry.ChineseName.AfterFirst(" (") & If(Entry.CurseForgeSlug, "") & If(Entry.ModrinthSlug, ""), 0.5)
}
})
Next
'获取搜索结果
Dim SearchResults = Search(SearchEntries, Request.SearchText, 3)
Dim SearchResults = Search(SearchEntries, Request.SearchText, 40, 0.2)
If Not SearchResults.Any() Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
Dim SearchResult As String = ""
For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
If Not SearchResults(i).AbsoluteRight AndAlso i >= Math.Min(2, SearchResults.Count - 1) Then Exit For '把 3 个结果拼合以提高准确度
If SearchResults(i).Item.CurseForgeSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.CurseForgeSlug.Replace("-", " ").Replace("/", " ") & " "
If SearchResults(i).Item.ModrinthSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.ModrinthSlug.Replace("-", " ").Replace("/", " ") & " "
SearchResult += SearchResults(i).Item.ChineseName.AfterLast(" (").TrimEnd(") ").BeforeFirst(" - ").
Replace(":", "").Replace("(", "").Replace(")", "").ToLower.Replace("/", " ") & " "
'提取可能的英文名
Dim ExtractWords =
Function(Result As SearchEntry(Of CompDatabaseEntry)) As String()
Dim Word As String = ""
If Result.Item.CurseForgeSlug IsNot Nothing Then Word += Result.Item.CurseForgeSlug.Replace("-", " ").Replace("/", " ") & " "
If Result.Item.ModrinthSlug IsNot Nothing Then Word += Result.Item.ModrinthSlug.Replace("-", " ").Replace("/", " ") & " "
Word += Result.Item.ChineseName.AfterLast(" (").TrimEnd(") ").BeforeFirst(" - ").
Replace(":", "").Replace("(", "").Replace(")", "").ToLower.Replace("/", " ").Replace("-", " ")
Dim Words = Word.ToLower.Split(" ")
Words = Words.Select(Function(w) w.TrimStart("{[(").TrimEnd("}])")).Where(
Function(w)
If w.Length <= 1 Then Return False '单字词
If {"the", "of", "mod", "and"}.Contains(w) Then Return False '常见词
If Val(w) > 0 Then Return False '数字
If w.Split(" ").Count > 3 AndAlso w.Contains("ftb") Then Return False '神秘 FTB
Return True
End Function).Distinct.ToArray
Return Words
End Function
Dim WordWeights As New Dictionary(Of String, Double) '各个单词及其出现的权重
For Each Result In SearchResults
For Each Word In ExtractWords(Result)
Dim Similarity = If(Result.SearchSource.Any(Function(s) s.Aliases.Contains(Request.SearchText)), 100000, Result.Similarity) '完全匹配为 100000
If Not WordWeights.ContainsKey(Word) Then WordWeights.Add(Word, 0)
WordWeights(Word) += Similarity
Next
Next
Log("[Comp] 中文搜索原始关键词:" & SearchResult, LogLevel.Developer)
'去除常见连接词
Dim RealFilter As String = ""
For Each Word In SearchResult.Split(" ")
If {"the", "of", "a", "mod", "and"}.Contains(Word.ToLowerInvariant) OrElse Val(Word) > 0 Then Continue For
If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For
RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " "
Next
Request.SearchText = RealFilter
Log("[Comp] 中文搜索最终关键词:" & RealFilter, LogLevel.Developer)
If Not WordWeights.Any() Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
'根据权重选取英文单词
Dim SortedWords = WordWeights.OrderByDescending(Function(w) w.Value).ToList
If SortedWords.First.Value >= 100000 Then '如果有完全匹配的,就只选完全匹配的
Request.SearchText = SortedWords.Where(Function(w) w.Value >= 100000).Select(Function(w) w.Key).Join(" ")
Else '否则,CurseForge 选第一个结果,Modrinth 选前 5 个单词
Request.SearchText = SortedWords.Take(5).Select(Function(w) w.Key).Join(" ")
Request.CurseForgeAltSearchText = ExtractWords(SearchResults.First).Join(" ")
Log("[Comp] 中文搜索基础关键词(CurseForge):" & Request.CurseForgeAltSearchText, LogLevel.Developer)
End If
Log("[Comp] 中文搜索基础关键词:" & Request.SearchText, LogLevel.Developer)
End If
'驼峰英文请求关键字处理
Dim SpacedKeywords = Request.SearchText.RegexReplace("([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)", "$& ")
Dim ConnectedKeywords = Request.SearchText.Replace(" ", "")
Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Request.SearchText, ConnectedKeywords & " " & RawFilter)).ToLower
'最终处理关键字:分割、去重
Dim RightKeywords As New List(Of String)
For Each Keyword In AllPossibleKeywords.Split(" ")
Keyword = Keyword.Trim("["c, "]"c)
If Keyword = "" Then Continue For
If {"forge", "fabric", "for", "mod", "quilt"}.Contains(Keyword) Then '#208
Log("[Comp] 已跳过搜索关键词:" & Keyword, LogLevel.Developer)
Continue For
Dim ProcessKeywords =
Sub(ByRef Text As String)
If Text Is Nothing Then Return
Text = Text.ToLowerInvariant
Dim Words As New List(Of String)
For Each Keyword In Text.Split(" ")
Keyword = Keyword.Trim("["c, "]"c)
If Keyword = "" Then Continue For
If {"forge", "fabric", "for", "mod", "quilt"}.Contains(Keyword) Then '#208
Log("[Comp] 已跳过搜索关键词:" & Keyword, LogLevel.Developer)
Continue For
End If
Words.Add(Keyword)
Next
If RawFilter.Length > 0 AndAlso Not Words.Any() Then
Text = RawFilter '全都被过滤掉了
Else
Text = Join(Words.Distinct.ToList, " ")
End If
RightKeywords.Add(Keyword)
Next
If RawFilter.Length > 0 AndAlso Not RightKeywords.Any() Then
Request.SearchText = RawFilter '全都被过滤掉了
Else
Request.SearchText = Join(RightKeywords.Distinct.ToList, " ").ToLower
End If
'例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
If RawFilter.Replace(" ", "").ContainsF("optiforge", True) Then Text = "optiforge"
If RawFilter.Replace(" ", "").ContainsF("optifabric", True) Then Text = "optifabric"
End Sub
'例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
If RawFilter.Replace(" ", "").ContainsF("optiforge", True) Then Request.SearchText = "optiforge"
If RawFilter.Replace(" ", "").ContainsF("optifabric", True) Then Request.SearchText = "optifabric"
If Request.CurseForgeAltSearchText IsNot Nothing Then
ProcessKeywords(Request.CurseForgeAltSearchText)
Log("[Comp] 工程列表搜索最终文本(CurseForge):" & Request.CurseForgeAltSearchText, LogLevel.Debug)
End If
ProcessKeywords(Request.SearchText)
Log("[Comp] 工程列表搜索最终文本:" & Request.SearchText, LogLevel.Debug)
Task.Progress = 0.1
@@ -1195,7 +1224,7 @@ Retry:
Not Storage.Results.Any(Function(b) r.IsLike(b))).ToList
'加入列表
RealResults.AddRange(RawResults)
Log($"[Comp] 去重、筛选后累计新增结果 {RealResults.Count} 个")
Log($"[Comp] 去重、筛选后累计新增结果 {RealResults.Count} 个(目前已有结果 {Storage.Results.Count} 个)")
#End Region
@@ -1241,16 +1270,19 @@ Retry:
For Each Result As CompProject In RealResults
Scores.Add(Result, If(Result.WikiId > 0, 0.2, 0) +
Math.Log10(Math.Max(Result.DownloadCount, 1) * GetDownloadCountMult(Result)) / 9)
Entry.Add(New SearchEntry(Of CompProject) With {.Item = Result, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
New KeyValuePair(Of String, Double)(If(IsChineseSearch, Result.TranslatedName, Result.RawName), 1),
New KeyValuePair(Of String, Double)(Result.Description, 0.05)}})
Entry.Add(New SearchEntry(Of CompProject) With {.Item = Result, .SearchSource = New List(Of SearchSource) From {
New SearchSource(If(IsChineseSearch, Result.TranslatedName, Result.RawName).Split({"/"c}, StringSplitOptions.RemoveEmptyEntries), 1),
New SearchSource(Result.Description, 0.05)}})
Next
Dim SearchResult = Search(Entry, RawFilter, 101, -1)
For Each OneResult In SearchResult
Scores(OneResult.Item) += OneResult.Similarity / SearchResult(0).Similarity '最高 1 分的相似度分
Scores(OneResult.Item) +=
If(OneResult.AbsoluteRight, 10, OneResult.Similarity) /
If(SearchResult.First.AbsoluteRight, 10, SearchResult.First.Similarity) '最高 1 分的相似度分
Next
End If
'根据排序分得出结果并添加
If Task.IsAborted Then Throw New ThreadInterruptedException '#8246
Storage.Results.AddRange(
Scores.OrderByDescending(Function(s) s.Value).Select(Function(r) r.Key))
@@ -1430,7 +1462,10 @@ Retry:
End If
'GameVersions
Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.ToLower).ToList
GameVersions = RawVersions.Where(Function(v) McVersion.IsFormatFit(v)).Select(Function(v) v.Replace("-snapshot", " 预览版")).ToList
GameVersions = RawVersions.
Where(Function(v) McVersion.IsFormatFit(v)).
Select(Function(v) v.Replace("-snapshot", " 预览版")).
Distinct.ToList
If GameVersions.Count > 1 Then
GameVersions = GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本
@@ -1492,7 +1527,8 @@ Retry:
'GameVersions
Dim RawVersions As List(Of String) = Data("game_versions").Select(Function(t) t.ToString.Trim.ToLower).ToList
GameVersions = RawVersions.Where(Function(v) v.Contains(".")).
Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).ToList
Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).
Distinct.ToList
If GameVersions.Count > 1 Then
GameVersions = GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本
@@ -1648,7 +1684,7 @@ Retry:
Next
Else
'Modrinth
ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version")
ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version?include_changelog=false")
End If
CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
Where(Function(a) a.Available).ToList.
@@ -1176,17 +1176,17 @@
Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
Case 1
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) '至少 10s,要不然有时候远端服务器来不及完成
Urls.Add(New KeyValuePair(Of String, Integer)(Url, If(Url.Contains("modrinth"), 20, 10))) '至少 20s,要不然 Modrinth 返回不过来
Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
Case Else
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, If(Url.Contains("modrinth"), 20, 10)))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
End Select
Else
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, If(Url.Contains("modrinth"), 20, 10)))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
End If
Dim Exs As String = ""
@@ -95,6 +95,7 @@
Try
'确定文件存在
If Not File.Exists(PathJava) Then Throw New FileNotFoundException("未找到 java.exe 文件", PathJava)
If PathJava.ContainsF("finalshell", True) Then Throw New Exception("不兼容 FinalShell 的 Java") '#8080
If File.Exists(PathFolder & "pdf-bookmark") Then Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326
IsJre = Not File.Exists(PathFolder & "javac.exe")
'运行 -version
@@ -1314,40 +1314,6 @@ Retry:
Private McLaunchArgument As String
''' <summary>
''' 释放 Java Wrapper 并返回完整文件路径。
''' </summary>
Public Function ExtractJavaWrapper() As String
Dim WrapperPath As String = PathPure & "JavaWrapper.jar"
Log("[Java] 选定的 Java Wrapper 路径:" & WrapperPath)
SyncLock ExtractJavaWrapperLock '避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex As Exception
If File.Exists(WrapperPath) Then
'因为未知原因 Java Wrapper 可能变为只读文件(#4243)
Log(ex, "Java Wrapper 文件释放失败,但文件已存在,将在删除后尝试重新生成", LogLevel.Developer)
Try
File.Delete(WrapperPath)
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex2 As Exception
Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", LogLevel.Developer)
WrapperPath = PathPure & "JavaWrapper2.jar"
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex3 As Exception
Throw New FileNotFoundException("释放 Java Wrapper 最终尝试失败", ex3)
End Try
End Try
Else
Throw New FileNotFoundException("释放 Java Wrapper 失败", ex)
End If
End Try
End SyncLock
Return WrapperPath
End Function
Private ExtractJavaWrapperLock As New Object
'主方法,合并 Jvm、Game、Replace 三部分的参数数据
Private Sub McLaunchArgumentMain(Loader As LoaderTask(Of String, List(Of McLibToken)))
McLaunchLog("开始获取 Minecraft 启动参数")
@@ -1393,6 +1359,8 @@ Retry:
'自定义
Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Instance:=McInstanceSelected)
Arguments += " " & ArgumentReplace(If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame))
'GC
Arguments = ProcessGCArguments(Arguments, McLaunchJavaSelected, McInstanceSelected)
'进行参数替换
Dim ReplaceArguments = McLaunchArgumentsReplace(McInstanceSelected, Loader)
If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
@@ -1754,6 +1722,72 @@ NextInstance:
Return GameArguments
End Function
'工具方法
''' <summary>
''' 释放 Java Wrapper 并返回完整文件路径。
''' </summary>
Public Function ExtractJavaWrapper() As String
Dim WrapperPath As String = PathPure & "JavaWrapper.jar"
Log("[Java] 选定的 Java Wrapper 路径:" & WrapperPath)
Static Lock As New Object
SyncLock Lock '避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex As Exception
If File.Exists(WrapperPath) Then
'因为未知原因 Java Wrapper 可能变为只读文件(#4243)
Log(ex, "Java Wrapper 文件释放失败,但文件已存在,将在删除后尝试重新生成", LogLevel.Developer)
Try
File.Delete(WrapperPath)
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex2 As Exception
Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", LogLevel.Developer)
WrapperPath = PathPure & "JavaWrapper2.jar"
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex3 As Exception
Throw New FileNotFoundException("释放 Java Wrapper 最终尝试失败", ex3)
End Try
End Try
Else
Throw New FileNotFoundException("释放 Java Wrapper 失败", ex)
End If
End Try
End SyncLock
Return WrapperPath
End Function
''' <summary>
''' 根据设置的 GC 方式返回对应的 JVM 参数。
''' </summary>
Private Function ProcessGCArguments(Arguments As String, SelectedJava As JavaEntry, Optional TargetInstance As McInstance = Nothing) As String
'0:在 Java 21+ 使用分代 ZGC,20~15 使用非分代的 ZGC,14- 使用 G1GC
'1:在 Java 21+ 使用分代 ZGC,20- 使用 G1GC
'2:仅 G1GC
'3:不指定
Dim SetupType As Integer = Setup.Get("LaunchAdvanceGC")
If TargetInstance IsNot Nothing AndAlso Setup.Get("VersionAdvanceGC", TargetInstance) > 0 Then SetupType = Setup.Get("VersionAdvanceGC", TargetInstance) - 1 '去掉默认
If SetupType = 3 Then Return Arguments.Trim '不指定时直接返回
Dim UseG1GC As Boolean =
(SetupType = 0 AndAlso SelectedJava.MajorVersion < 15) OrElse
(SetupType = 1 AndAlso SelectedJava.MajorVersion < 21) OrElse
SetupType = 2
McLaunchLog($"GC 设置:{SetupType},选取 {If(UseG1GC, "G1GC", "ZGC")},Java 版本:{SelectedJava.MajorVersion}")
'移除
Arguments = RegexReplace(Arguments,
"( )*-XX:[+-]?(Use\w+GC|ZGenerational|UseCompactObjectHeaders|G1\w+Percent|G1\w+Size|MaxGCPauseMillis)(=[^\s]+)?", "").Trim
'添加
If UseG1GC Then
Arguments = Arguments.Replace("-XX:+UnlockExperimentalVMOptions", "")
Arguments = "-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M " + Arguments
Else
Arguments = "-XX:+UseZGC " + Arguments
If SelectedJava.MajorVersion = 21 OrElse SelectedJava.MajorVersion = 22 Then Arguments = "-XX:+ZGenerational " + Arguments 'Java 23 起默认启用分代 ZGC,不需要再添加参数
If SelectedJava.MajorVersion >= 25 Then Arguments = "-XX:+UseCompactObjectHeaders " + Arguments
End If
Return Arguments.Trim
End Function
#End Region
#Region "解压 Natives"
@@ -1426,7 +1426,7 @@ OnLoaded:
'确认最新版本,若为快照则加入常用列表
Dim LargestInstance As McInstance = InstanceList.
Where(Function(v) v.State = McInstanceState.Original OrElse v.State = McInstanceState.Snapshot).
MaxOf(Function(v) v.ReleaseTime)
MaxBy(Function(v) v.ReleaseTime)
If LargestInstance IsNot Nothing AndAlso LargestInstance.State = McInstanceState.Snapshot Then
InstanceUseful.Add(LargestInstance)
InstanceList.Remove(LargestInstance)
@@ -636,9 +636,8 @@ Retry:
Dim Request As New McInstallRequest With {.NewInstanceName = InstanceName, .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\"}
For Each Component As JObject In PackJson("components")
If Not Component.ContainsKey("uid") Then Continue For
Select Case Component("uid").ToString
Case "org.lwjgl"
Log("[ModPack] 已跳过 LWJGL 项")
Dim UID As String = Component("uid").ToString
Select Case UID
Case "net.minecraft"
Request.MinecraftName = Component("version")
Case "net.minecraftforge"
@@ -647,9 +646,13 @@ Retry:
Request.NeoForgeVersion = Component("version")
Case "net.fabricmc.fabric-loader"
Request.FabricVersion = Component("version")
Case Else
'Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
NotifyIncompatibleLoader(Component("uid"))
Case Else
If UID.StartsWithF("org.lwjgl") Then '#8210
Log("[ModPack] 已跳过 LWJGL 项:" & UID)
Else
NotifyIncompatibleLoader(UID)
End If
End Select
Next
'构造加载器