ModJava.vb 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. Public Module ModJava
  2. Public JavaListCacheVersion As Integer = 7
  3. ''' <summary>
  4. ''' 目前所有可用的 Java。
  5. ''' </summary>
  6. Public JavaList As New List(Of JavaEntry)
  7. Public Class JavaEntry
  8. '路径
  9. ''' <summary>
  10. ''' java.exe 文件的完整路径。
  11. ''' </summary>
  12. Public ReadOnly Property PathJava As String
  13. Get
  14. Return PathFolder & "java.exe"
  15. End Get
  16. End Property
  17. ''' <summary>
  18. ''' java.exe 文件所在文件夹的路径,以 \ 结尾。
  19. ''' </summary>
  20. Public PathFolder As String
  21. ''' <summary>
  22. ''' 是否为用户手动导入的 Java。
  23. ''' </summary>
  24. Public IsUserImport As Boolean
  25. '版本信息
  26. ''' <summary>
  27. ''' Java 的详细版本。若不足 4 位会在前方补 1,例如 1.16.0.1。
  28. ''' 其大版本号为 Minor。
  29. ''' </summary>
  30. Public Version As Version
  31. ''' <summary>
  32. ''' Java 的大版本号。
  33. ''' </summary>
  34. Public ReadOnly Property MajorVersion As Integer
  35. Get
  36. Return Version.Minor
  37. End Get
  38. End Property
  39. ''' <summary>
  40. ''' 是否为 Java Runtime Environment。
  41. ''' </summary>
  42. Public IsJre As Boolean
  43. ''' <summary>
  44. ''' 是否为 64 位 Java。
  45. ''' </summary>
  46. Public Is64Bit As Boolean
  47. ''' <summary>
  48. ''' 是否已设置环境变量。
  49. ''' </summary>
  50. Public ReadOnly Property HasEnvironment As Boolean
  51. Get
  52. If PathFolder Is Nothing OrElse PathEnv Is Nothing Then Return False
  53. Return PathEnv.Replace("\", "").Replace("/", "").ContainsIgnoreCase(PathFolder.Replace("\", ""))
  54. End Get
  55. End Property
  56. '序列化
  57. Public Function ToJson() As JObject
  58. Return New JObject({New JProperty("Path", PathFolder), New JProperty("VersionString", Version.ToString), New JProperty("IsJre", IsJre), New JProperty("Is64Bit", Is64Bit), New JProperty("IsUserImport", IsUserImport)})
  59. End Function
  60. Public Shared Function FromJson(Data As JObject) As JavaEntry
  61. Return New JavaEntry(Data("Path"), Data("IsUserImport")) With {.Version = New Version(Data("VersionString")), .IsJre = Data("IsJre"), .Is64Bit = Data("Is64Bit")}
  62. End Function
  63. ''' <summary>
  64. ''' 转化为用户友好的字符串输出。
  65. ''' </summary>
  66. Public Overrides Function ToString() As String
  67. Dim VersionString = Version.ToString
  68. If VersionString.StartsWithF("1.") Then VersionString = VersionString.Substring(2)
  69. Return If(IsJre, "JRE ", "JDK ") & MajorVersion & " (" & VersionString & ")" & If(Is64Bit, "", ",32 位") & If(IsUserImport, ",手动导入", "") & ":" & PathFolder
  70. End Function
  71. '构造
  72. ''' <summary>
  73. ''' 输入 javaw.exe 文件所在文件夹的路径,不限制结尾。
  74. ''' </summary>
  75. Public Sub New(Folder As String, IsUserImport As Boolean)
  76. PathFolder = PathUtils.AddSlashSuffix(PathUtils.ForCompare(Folder))
  77. Me.IsUserImport = IsUserImport
  78. End Sub
  79. '方法
  80. Private IsChecked As Boolean = False
  81. ''' <summary>
  82. ''' 检查并获取 Java 详细信息。在 Java 存在异常时抛出错误。
  83. ''' </summary>
  84. Public Sub Check()
  85. If IsChecked Then Return
  86. Dim Output As String = Nothing
  87. Try
  88. '确定文件存在
  89. If Not FileUtils.Exists(PathJava) Then Throw New FileNotFoundException("未找到 java.exe 文件", PathJava)
  90. If {"finalshell", "Paranoia File"}.Any(Function(n) PathJava.ContainsIgnoreCase(n)) Then Throw New Exception("不兼容该精简版 Java") '#2249、#8080
  91. If FileUtils.Exists(PathFolder & "pdf-bookmark") Then Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326
  92. IsJre = Not FileUtils.Exists(PathFolder & "javac.exe")
  93. '运行 -version
  94. Output = StartProcessAndGetOutput(PathJava, "-version", 15000).Lower
  95. If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败")
  96. Logger.Trace($"Java 检查输出:{PathJava}{vbCrLf}{Output}")
  97. If Output.Contains("/lib/ext exists") Then Throw New ApplicationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试")
  98. If Output.Contains("a fatal error") Then Throw New ApplicationException("无法运行该 Java,该 Java 或系统存在问题")
  99. '获取详细信息
  100. Dim VersionString = If(Output.RegexSeek("(?<=version "")[^""]+"), If(Output.RegexSeek("(?<=openjdk )[0-9]+"), "")).Replace("_", ".").Split("-").First
  101. If VersionString.Split(".").Count > 4 Then VersionString = VersionString.Replace(".0.", ".") '#3493,VersionString = "21.0.2.0.2"
  102. Do While VersionString.Split(".").Count < 4
  103. If VersionString.StartsWithF("1.") Then
  104. VersionString = VersionString & ".0"
  105. Else
  106. VersionString = "1." & VersionString
  107. End If
  108. Loop
  109. If VersionString = "" Then Throw New ApplicationException($"未找到该 Java 的版本号{If(Output.Length < 500, $"{vbCrLf}输出为:{vbCrLf}{Output}", "")}")
  110. Version = New Version(VersionString)
  111. If Version.Minor = 0 Then
  112. Logger.Info($"疑似 X.0.X.X 格式版本号:{Version}")
  113. Version = New Version(1, Version.Major, Version.Build, Version.Revision)
  114. End If
  115. Is64Bit = Output.Contains("64-bit")
  116. If Version.Minor <= 4 OrElse Version.Minor >= 100 Then Throw New ApplicationException("分析详细信息失败,获取的版本为 " & Version.ToString)
  117. '#3649:在 64 位系统上禁用 32 位 Java
  118. If Not Is64Bit AndAlso Not Is32BitSystem Then Throw New Exception("该 Java 为 32 位版本,请安装 64 位的 Java")
  119. Catch ex As ApplicationException
  120. Throw ex
  121. Catch ex As ThreadInterruptedException
  122. Throw ex
  123. Catch ex As Exception
  124. Logger.Info($"检查失败的 Java 输出:{If(PathJava, "Nothing")}{vbCrLf}{If(Output, "无程序输出")}")
  125. Throw New Exception("检查 Java 失败(" & If(PathJava, "Nothing") & ")", ex)
  126. End Try
  127. IsChecked = True
  128. End Sub
  129. End Class
  130. ''' <summary>
  131. ''' Path 环境变量。
  132. ''' </summary>
  133. Private ReadOnly Property PathEnv As String
  134. Get
  135. If _PathEnv Is Nothing Then _PathEnv = If(Environment.GetEnvironmentVariable("Path"), "")
  136. Return _PathEnv
  137. End Get
  138. End Property
  139. Private _PathEnv As String = Nothing
  140. ''' <summary>
  141. ''' JAVA_HOME 环境变量。
  142. ''' </summary>
  143. Private ReadOnly Property PathJavaHome As String
  144. Get
  145. If _PathJavaHome Is Nothing Then _PathJavaHome = If(Environment.GetEnvironmentVariable("JAVA_HOME"), "")
  146. Return _PathJavaHome
  147. End Get
  148. End Property
  149. Private _PathJavaHome As String = Nothing
  150. ''' <summary>
  151. ''' 初始化 Java 列表,但除非没有 Java,否则不进行检查。
  152. ''' </summary>
  153. Public Sub JavaListInit()
  154. JavaList = New List(Of JavaEntry)
  155. Try
  156. If Settings.Get(Of Integer)("CacheJavaListVersion") < JavaListCacheVersion Then
  157. '不使用缓存
  158. Logger.Info("要求 Java 列表缓存更新")
  159. Settings.Set("CacheJavaListVersion", JavaListCacheVersion)
  160. Else
  161. '使用缓存
  162. For Each JsonEntry In GetJson(Settings.Get(Of String)("LaunchArgumentJavaAll"))
  163. JavaList.Add(JavaEntry.FromJson(JsonEntry))
  164. Next
  165. End If
  166. If Not JavaList.Any() Then
  167. Logger.Warn("初始化未找到可用的 Java,将自动触发搜索")
  168. JavaSearchLoader.Start(0)
  169. Else
  170. Logger.Info($"缓存中有 {JavaList.Count} 个可用的 Java:")
  171. JavaList.ForEach(Sub(j) Logger.Info($"- {j}"))
  172. End If
  173. Catch ex As Exception
  174. Logger.Error(ex, "初始化 Java 列表失败")
  175. Settings.Set("LaunchArgumentJavaAll", "[]")
  176. End Try
  177. End Sub
  178. ''' <summary>
  179. ''' 防止多个需要 Java 的部分同时要求下载 Java(#3797)。
  180. ''' </summary>
  181. Public JavaLock As New Object
  182. ''' <summary>
  183. ''' 根据要求返回最适合的 Java,若找不到则返回 Nothing。
  184. ''' 最小与最大版本在与输入相同时也会通过。
  185. ''' 必须在工作线程调用,且必须包括 SyncLock JavaLock。
  186. ''' </summary>
  187. Public Function JavaSelect(CancelException As String, Optional MinVersion As Version = Nothing, Optional MaxVersion As Version = Nothing,
  188. Optional GameInstance As McInstance = Nothing) As JavaEntry
  189. Try
  190. Dim AllowedJavaList As New List(Of JavaEntry)
  191. '添加特定的 Java
  192. Dim JavaPreList As New Dictionary(Of String, Boolean)
  193. If McFolderSelected.Split("\").Count > 3 AndAlso Not McFolderSelected.Contains("AppData\Roaming") Then
  194. JavaSearchFolder(PathUtils.AddSlashSuffix(PathUtils.RemoveLastPart(McFolderSelected)), JavaPreList, False, True) 'Minecraft 文件夹的父文件夹(如果不是根目录或 %APPDATA% 的话)
  195. End If
  196. JavaSearchFolder(McFolderSelected, JavaPreList, False, True) 'Minecraft 文件夹
  197. JavaPreList = JavaPreList.Where(Function(j) Not j.Key.Contains(".minecraft\runtime")).
  198. ToDictionary(Function(j) j.Key, Function(j) j.Value) '排除官启自带 Java(#4286)
  199. If GameInstance IsNot Nothing Then JavaSearchFolder(GameInstance.PathVersion, JavaPreList, False, True) '所选版本文件夹
  200. Dim TargetJavaList As New List(Of JavaEntry)
  201. For Each Entry In JavaPreList
  202. TargetJavaList.Add(New JavaEntry(Entry.Key, Entry.Value))
  203. Next
  204. '检查特定的 Java
  205. If TargetJavaList.Any Then
  206. TargetJavaList = JavaCheckList(TargetJavaList)
  207. Logger.Info($"检查后找到 {TargetJavaList.Count} 个特定路径下的 Java:")
  208. For Each Java In TargetJavaList
  209. Logger.Info($"- {Java}")
  210. Next
  211. End If
  212. #Region "添加用户指定的 Java,储存到 UserJava 中"
  213. Dim UserJava As JavaEntry = Nothing
  214. '获取版本独立设置中指定的 Java
  215. Dim VersionSelect As String = ""
  216. If GameInstance IsNot Nothing Then
  217. VersionSelect = Settings.Get(Of String)("VersionArgumentJavaSelect", Instance:=GameInstance)
  218. If VersionSelect.StartsWithF("{") Then
  219. Try
  220. UserJava = JavaEntry.FromJson(GetJson(VersionSelect))
  221. UserJava.Check()
  222. Catch ex As ThreadInterruptedException
  223. Throw
  224. Catch ex As Exception
  225. UserJava = Nothing
  226. Settings.Reset("VersionArgumentJavaSelect", Instance:=GameInstance)
  227. Logger.Error(ex, "版本独立设置中指定的 Java 已无法使用,此设置已重置", LogBehavior.Toast)
  228. End Try
  229. End If
  230. End If
  231. '获取全局设置中指定的 Java
  232. If UserJava Is Nothing AndAlso VersionSelect <> "" AndAlso Settings.Get(Of String)("LaunchArgumentJavaSelect") <> "" Then
  233. Try
  234. UserJava = JavaEntry.FromJson(GetJson(Settings.Get(Of String)("LaunchArgumentJavaSelect")))
  235. UserJava.Check()
  236. Catch ex As ThreadInterruptedException
  237. Throw
  238. Catch ex As Exception
  239. UserJava = Nothing
  240. Settings.Reset("LaunchArgumentJavaSelect")
  241. Logger.Error(ex, "全局设置中指定的 Java 已无法使用,此设置已重置", LogBehavior.Toast)
  242. End Try
  243. End If
  244. '添加到特定 Java 列表
  245. If UserJava IsNot Nothing Then
  246. Logger.Info($"用户指定的 Java:{UserJava}")
  247. TargetJavaList.Add(UserJava)
  248. End If
  249. #End Region
  250. RetryGet:
  251. '等待进行中的搜索结束
  252. If JavaSearchLoader.State <> LoadState.Finished AndAlso JavaSearchLoader.State <> LoadState.Waiting Then JavaSearchLoader.WaitForExit()
  253. Select Case JavaSearchLoader.State
  254. Case LoadState.Failed
  255. Throw JavaSearchLoader.Error
  256. Case LoadState.Interrupted
  257. Throw New ThreadInterruptedException("Java 搜索加载器已中断")
  258. End Select
  259. '生成完整的 Java 列表
  260. Dim AllJavaList As New List(Of JavaEntry)
  261. AllJavaList.AddRange(TargetJavaList)
  262. AllJavaList.AddRange(JavaList)
  263. '根据选定条件进行过滤
  264. For Each Java In AllJavaList
  265. If MinVersion IsNot Nothing AndAlso Java.Version < MinVersion Then Continue For
  266. If MaxVersion IsNot Nothing AndAlso Java.Version > MaxVersion Then Continue For
  267. If Java.Is64Bit AndAlso Is32BitSystem Then Continue For
  268. AllowedJavaList.Add(Java)
  269. Next
  270. '若未找到适合的 Java,尝试触发搜索
  271. If Not AllowedJavaList.Any() AndAlso JavaSearchLoader.State = LoadState.Waiting Then
  272. Logger.Info("未找到满足条件的 Java,尝试进行搜索")
  273. JavaSearchLoader.Start(IsForceRestart:=True)
  274. GoTo RetryGet
  275. End If
  276. #Region "检查用户指定的 Java 是否可用"
  277. '确保指定的 Java 可用
  278. If UserJava Is Nothing Then GoTo ExitUserJavaCheck
  279. If AllowedJavaList.Any(Function(j) j.PathFolder = UserJava.PathFolder) Then
  280. Logger.Info($"使用用户指定的 Java:{UserJava.PathFolder}")
  281. AllowedJavaList = New List(Of JavaEntry) From {UserJava}
  282. GoTo UserPass
  283. End If
  284. '指定的 Java 不可用,弹窗要求选择
  285. Logger.Info($"发现用户指定的不兼容 Java:{UserJava}")
  286. Logger.Info($"目前实际可用的 Java 列表:")
  287. For Each Java In AllowedJavaList
  288. Logger.Info($"- {Java}")
  289. Next
  290. Dim Requirement As String = ""
  291. Dim ShowRevision As Boolean = False
  292. If (MinVersion Is Nothing OrElse MinVersion.Minor = 0) AndAlso (MaxVersion IsNot Nothing AndAlso MaxVersion.Minor < 999) Then
  293. ShowRevision = MaxVersion.MinorRevision < 999
  294. Requirement = "最高兼容到 Java " & MaxVersion.Minor & If(ShowRevision, "." & MaxVersion.MajorRevision & "." & MaxVersion.MinorRevision, "")
  295. ElseIf (MinVersion IsNot Nothing AndAlso MinVersion.Minor > 0) AndAlso (MaxVersion Is Nothing OrElse MaxVersion.Minor >= 999) Then
  296. ShowRevision = MinVersion.MinorRevision > 0 OrElse MinVersion.MajorRevision > 0
  297. Requirement = "至少需要 Java " & MinVersion.Minor & If(ShowRevision, "." & MinVersion.MajorRevision & "." & MinVersion.MinorRevision, "")
  298. ElseIf (MinVersion IsNot Nothing AndAlso MinVersion.Minor > 0) AndAlso (MaxVersion IsNot Nothing AndAlso MaxVersion.Minor < 999) Then
  299. ShowRevision = MinVersion.MinorRevision > 0 OrElse MinVersion.MajorRevision > 0 OrElse MaxVersion.MinorRevision < 999
  300. Dim Left As String = MinVersion.Minor & If(ShowRevision, "." & MinVersion.MajorRevision & "." & MinVersion.MinorRevision, "")
  301. Dim Right As String = MaxVersion.Minor & If(ShowRevision, "." & MaxVersion.MajorRevision & "." & MaxVersion.MinorRevision, "")
  302. Requirement = "需要 Java " & If(Left = Right, Left, Left & " ~ " & Right)
  303. End If
  304. Dim JavaCurrent As String = UserJava.MajorVersion & If(ShowRevision, "." & UserJava.Version.MajorRevision & "." & UserJava.Version.MinorRevision, "")
  305. If GameInstance IsNot Nothing AndAlso Settings.Get(Of Boolean)("VersionAdvanceJava", GameInstance) Then
  306. '直接跳过弹窗
  307. Logger.Warn($"设置中指定了使用 Java {JavaCurrent},但当前版本{Requirement},这可能会导致游戏崩溃!")
  308. AllowedJavaList = New List(Of JavaEntry) From {UserJava}
  309. Else
  310. Select Case MyMsgBox("你在设置中手动指定了使用 Java " & JavaCurrent & ",但当前" & Requirement & "。" & vbCrLf &
  311. "如果强制使用该 Java,可能导致游戏崩溃。" & vbCrLf &
  312. "你也可以将 游戏 Java 设置修改为 自动选择。" & vbCrLf &
  313. vbCrLf &
  314. " - 指定的 Java:" & UserJava.ToString,
  315. "Java 兼容性警告", "让 PCL 自动选择", "强制使用该 Java", "取消")
  316. Case 1 '让 PCL 自动选择
  317. Case 2 '强制使用指定的 Java
  318. Logger.Info("已强制使用用户指定的不兼容 Java")
  319. AllowedJavaList = New List(Of JavaEntry) From {UserJava}
  320. Case 3 '取消启动
  321. Throw New Exception(CancelException)
  322. End Select
  323. End If
  324. ExitUserJavaCheck:
  325. #End Region
  326. '若依然未找到适合的 Java,直接返回
  327. If Not AllowedJavaList.Any() Then Return Nothing
  328. '优先使用特定目录下的 Java
  329. For Each Java In AllowedJavaList
  330. '如果在官启文件夹启动,会将官启自带 Java 错误视作 MC 文件夹指定 Java,导致了 #2054 的第二例
  331. If Java.PathFolder.Contains(".minecraft\cache\java") Then Continue For
  332. If Java.PathFolder.Contains("PCL\MyDownload\") Then Continue For '#5780
  333. If TargetJavaList.Contains(Java) Then
  334. '直接使用指定的 Java
  335. AllowedJavaList = New List(Of JavaEntry) From {Java}
  336. Logger.Info($"优先使用特定路径下的 Java:{Java}")
  337. GoTo UserPass
  338. End If
  339. Next
  340. UserPass:
  341. '对适合的 Java 进行排序
  342. AllowedJavaList = AllowedJavaList.SortByComparison(AddressOf JavaSorter)
  343. Logger.Info($"排序后的 Java 优先顺序:")
  344. For Each Java In AllowedJavaList
  345. Logger.Info($"- {Java}")
  346. Next
  347. '检查选定的 Java,若测试失败则尝试进行搜索
  348. Dim SelectedJava = AllowedJavaList.First
  349. Try
  350. SelectedJava.Check()
  351. Catch ex As ThreadInterruptedException
  352. Throw
  353. Catch ex As Exception
  354. If ex.InnerException IsNot Nothing AndAlso TypeOf ex.InnerException Is ThreadInterruptedException Then Throw ex.InnerException
  355. Logger.Warn(ex, "最终选定的 Java 已无法使用,尝试进行搜索")
  356. AllowedJavaList = New List(Of JavaEntry)
  357. JavaSearchLoader.Start(IsForceRestart:=True)
  358. GoTo RetryGet
  359. End Try
  360. '返回
  361. Logger.Info($"最终选定的 Java:{AllowedJavaList.First}")
  362. Return SelectedJava
  363. Catch ex As ThreadInterruptedException
  364. Logger.Warn(ex, "查找符合条件的 Java 时出现加载器中断")
  365. Return Nothing
  366. Catch ex As Exception
  367. If ex.Message = "$$" Then Throw ex
  368. Logger.Error(ex, "查找符合条件的 Java 失败")
  369. Return Nothing
  370. End Try
  371. End Function
  372. ''' <summary>
  373. ''' 是否强制指定了 64 位 Java。如果没有强制指定,返回是否安装了 64 位 Java。
  374. ''' </summary>
  375. Public Function JavaIs64Bit(Optional GameInstance As McInstance = Nothing) As Boolean
  376. Try
  377. '检查强制指定
  378. Dim UserSetup As String = Settings.Get(Of String)("LaunchArgumentJavaSelect")
  379. If GameInstance IsNot Nothing Then
  380. Dim UserSetupVersion As String = Settings.Get(Of String)("VersionArgumentJavaSelect", Instance:=GameInstance)
  381. If UserSetupVersion <> "使用全局设置" Then UserSetup = UserSetupVersion
  382. End If
  383. If UserSetup <> "" Then
  384. Dim UserJava As JavaEntry = Nothing
  385. Try
  386. UserJava = JavaEntry.FromJson(GetJson(UserSetup))
  387. Catch ex As Exception
  388. Logger.Warn(ex, "版本指定的 Java 信息已损坏,已重置版本设置中指定的 Java")
  389. Settings.Set("VersionArgumentJavaSelect", "使用全局设置", Instance:=GameInstance)
  390. GoTo NoUserJava
  391. End Try
  392. For Each Java In JavaList
  393. If Java.PathFolder = UserJava.PathFolder Then Return UserJava.Is64Bit
  394. Next
  395. End If
  396. NoUserJava:
  397. '检查列表
  398. For Each Java In JavaList
  399. If Java.Is64Bit Then Return True
  400. Next
  401. Return False
  402. Catch ex As Exception
  403. Logger.Error(ex, "检查 Java 类别时出错")
  404. Settings.Set("LaunchArgumentJavaSelect", "")
  405. Return True
  406. End Try
  407. End Function
  408. ''' <summary>
  409. ''' 将 Java 按照适用性排序。
  410. ''' </summary>
  411. Public Function JavaSorter(Left As JavaEntry, Right As JavaEntry) As Boolean
  412. '1. 尽量在当前文件夹或当前 Minecraft 文件夹
  413. Dim ProgramPathParent As String, MinecraftPathParent As String = ""
  414. Dim PathInfo = DirectoryUtils.GetInfo(PathExeFolder)
  415. ProgramPathParent = PathUtils.RemoveExtendedPrefix(If(PathInfo.Parent, PathInfo).FullName)
  416. Dim PathMcInfo = DirectoryUtils.GetInfo(McFolderSelected)
  417. If McFolderSelected <> "" Then MinecraftPathParent = PathUtils.RemoveExtendedPrefix(If(PathMcInfo.Parent, PathMcInfo).FullName)
  418. If Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Not Right.PathFolder.StartsWithF(ProgramPathParent) Then Return True
  419. If Not Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Right.PathFolder.StartsWithF(ProgramPathParent) Then Return False
  420. If McFolderSelected <> "" Then
  421. If Left.PathFolder.StartsWithF(MinecraftPathParent) AndAlso Not Right.PathFolder.StartsWithF(MinecraftPathParent) Then Return True
  422. If Not Left.PathFolder.StartsWithF(MinecraftPathParent) AndAlso Right.PathFolder.StartsWithF(MinecraftPathParent) Then Return False
  423. End If
  424. '2. 尽量使用 64 位
  425. If Left.Is64Bit AndAlso Not Right.Is64Bit Then Return True
  426. If Not Left.Is64Bit AndAlso Right.Is64Bit Then Return False
  427. '3. 尽量不使用 JDK
  428. If Left.IsJre AndAlso Not Right.IsJre Then Return True
  429. If Not Left.IsJre AndAlso Right.IsJre Then Return False
  430. '4. Java 大版本
  431. If Left.MajorVersion <> Right.MajorVersion Then
  432. ' Java 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  433. Dim Weight = {0, 1, 2, 3, 4, 5, 6, 14, 30, 10, 12, 15, 13, 9, 8, 7, 11, 31, 29, 16, 17} '更高的版本指定为 20,且越低越好
  434. Return If(Left.MajorVersion > 20, 20 - Left.MajorVersion * 0.0001, Weight.ElementAtOrDefault(Left.MajorVersion)) >=
  435. If(Right.MajorVersion > 20, 20 - Right.MajorVersion * 0.0001, Weight.ElementAtOrDefault(Right.MajorVersion))
  436. End If
  437. '5. 最次级版本号更接近 51
  438. Return Math.Abs(Left.Version.Revision - 51) <= Math.Abs(Right.Version.Revision - 51)
  439. End Function
  440. #Region "搜索"
  441. ''' <summary>
  442. ''' 模糊搜索并获取所有可用的 Java,并在结束后更新设置页面显示。输出将直接写入 JavaList。
  443. ''' </summary>
  444. Public JavaSearchLoader As New LoaderTask(Of Integer, Integer)("查找 Java", AddressOf JavaSearchLoaderSub) With {.ProgressWeight = 2}
  445. Private Sub JavaSearchLoaderSub(Loader As LoaderTask(Of Integer, Integer))
  446. If FrmSetupLaunch IsNot Nothing Then
  447. RunInUiWait(
  448. Sub()
  449. FrmSetupLaunch.ComboArgumentJava.Items.Clear()
  450. FrmSetupLaunch.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True})
  451. End Sub)
  452. End If
  453. If FrmInstanceSetup IsNot Nothing Then
  454. RunInUiWait(
  455. Sub()
  456. FrmInstanceSetup.ComboArgumentJava.Items.Clear()
  457. FrmInstanceSetup.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True})
  458. End Sub)
  459. End If
  460. Try
  461. '可能包含 Java 的文件夹列表,以 “\” 结尾,且仅包含 “\”
  462. 'Key:文件夹地址
  463. 'Value: 是否为玩家手动导入
  464. Dim JavaPreList As New Dictionary(Of String, Boolean)
  465. #Region "模糊查找可能可用的 Java"
  466. '查找环境变量中的 Java
  467. For Each PathInEnv As String In (PathEnv & ";" & PathJavaHome).Replace("\\", "\").Replace("/", "\").Split(";")
  468. PathInEnv = PathInEnv.Trim(" """.ToCharArray())
  469. If PathInEnv = "" Then Continue For
  470. If Not PathInEnv.EndsWithF("\") Then PathInEnv += "\"
  471. '粗略检查有效性
  472. If FileUtils.Exists(PathInEnv & "javaw.exe") Then JavaPreList(PathInEnv) = False
  473. Next
  474. '查找磁盘中的 Java
  475. For Each Disk As DriveInfo In DriveInfo.GetDrives()
  476. If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705)
  477. JavaSearchFolder(Disk.Name, JavaPreList, False)
  478. Next
  479. '查找用户文件夹中的 Java
  480. JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), JavaPreList, False)
  481. JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\.jdks\", JavaPreList, False, IsFullSearch:=True)
  482. JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\.sdkman\candidates\java\", JavaPreList, False, IsFullSearch:=True)
  483. '查找启动器目录中的 Java
  484. JavaSearchFolder(PathExeFolder, JavaPreList, False, IsFullSearch:=True)
  485. '查找所选 Minecraft 文件夹中的 Java
  486. If Not String.IsNullOrWhiteSpace(McFolderSelected) AndAlso PathExeFolder <> McFolderSelected Then JavaSearchFolder(McFolderSelected, JavaPreList, False, IsFullSearch:=True)
  487. '若不全为符号链接,则清除符号链接的地址
  488. Dim JavaWithoutReparse As New Dictionary(Of String, Boolean)
  489. For Each Pair In JavaPreList
  490. Dim Folder As String = Pair.Key.Replace("\\", "\").Replace("/", "\")
  491. Dim Info As FileSystemInfo = FileUtils.GetInfo(Folder & "javaw.exe")
  492. Do
  493. If Info.Attributes.HasFlag(FileAttributes.ReparsePoint) Then
  494. Logger.Info($"位于 {Folder} 的 Java 包含符号链接")
  495. Continue For
  496. End If
  497. Info = If(TypeOf Info Is FileInfo, CType(Info, FileInfo).Directory, CType(Info, DirectoryInfo).Parent)
  498. Loop While Info IsNot Nothing
  499. Logger.Info($"位于 {Folder} 的 Java 不含符号链接")
  500. JavaWithoutReparse.Add(Pair.Key, Pair.Value)
  501. Next
  502. If JavaWithoutReparse.Any Then JavaPreList = JavaWithoutReparse
  503. '若不全为特殊引用,则清除特殊引用的地址
  504. Dim JavaWithoutInherit As New Dictionary(Of String, Boolean)
  505. For Each Pair In JavaPreList
  506. If Pair.Key.Contains("java8path_target_") OrElse Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") OrElse Pair.Key.Contains("system32") Then
  507. Logger.Info($"位于 {Pair.Key} 的 Java 位于特殊路径,不应优先使用")
  508. Else
  509. JavaWithoutInherit.Add(Pair.Key, Pair.Value)
  510. End If
  511. Next
  512. If JavaWithoutInherit.Any Then JavaPreList = JavaWithoutInherit
  513. #End Region
  514. #Region "添加玩家手动导入的 Java"
  515. Dim ImportedJava As String = Settings.Get(Of String)("LaunchArgumentJavaAll")
  516. Try
  517. For Each JavaJsonObject In GetJson(ImportedJava)
  518. Dim Entry = JavaEntry.FromJson(JavaJsonObject)
  519. If Entry.IsUserImport Then JavaPreList(Entry.PathFolder) = True
  520. Next
  521. Catch ex As Exception
  522. Logger.Error(ex, "Java 列表已损坏")
  523. Settings.Set("LaunchArgumentJavaAll", "[]")
  524. End Try
  525. #End Region
  526. '确保可用并获取详细信息,转入正式列表
  527. Dim NewJavaList As New List(Of JavaEntry)
  528. For Each Entry In JavaPreList.ToList.DistinctBy(Function(a) a.Key.Lower) '#794
  529. NewJavaList.Add(New JavaEntry(Entry.Key, Entry.Value))
  530. Next
  531. NewJavaList = JavaCheckList(NewJavaList).SortByComparison(AddressOf JavaSorter)
  532. '修改设置项
  533. Dim AllList As New JArray
  534. For Each Java In NewJavaList
  535. AllList.Add(Java.ToJson)
  536. Next
  537. Settings.Set("LaunchArgumentJavaAll", AllList.ToString(Newtonsoft.Json.Formatting.None))
  538. JavaList = NewJavaList
  539. Catch ex As Exception
  540. Logger.Error(ex, "搜索 Java 时出错")
  541. JavaList = New List(Of JavaEntry)
  542. End Try
  543. Logger.Info($"Java 搜索完成,发现 {JavaList.Count} 个 Java")
  544. If FrmSetupLaunch IsNot Nothing Then RunInUi(Sub() FrmSetupLaunch.UpdateJavaComboBox())
  545. If FrmInstanceSetup IsNot Nothing Then RunInUi(Sub() FrmInstanceSetup.RefreshJavaComboBox())
  546. End Sub
  547. ''' <summary>
  548. ''' 多线程检查列表中的所有 Java 项。
  549. ''' </summary>
  550. Private Function JavaCheckList(JavaEntries As List(Of JavaEntry)) As List(Of JavaEntry)
  551. Logger.Info($"开始确认列表 Java 状态,共 {JavaEntries.Count} 项")
  552. Dim Result = New List(Of JavaEntry)
  553. Dim ListLock As New Object
  554. '启动检查线程
  555. Dim CheckThreads As New List(Of Thread)
  556. For Each Entry In JavaEntries
  557. Dim CheckThread As New Thread(
  558. Sub()
  559. Try
  560. Entry.Check()
  561. Logger.Trace($"- {Entry}")
  562. SyncLock ListLock
  563. Result.Add(Entry)
  564. End SyncLock
  565. Catch ex As ThreadInterruptedException
  566. Catch ex As Exception
  567. If Entry.IsUserImport Then
  568. Logger.Error(ex, $"位于 {Entry.PathFolder} 的 Java 存在异常,将被自动移除", LogBehavior.Toast)
  569. Else
  570. Logger.Warn(ex, $"位于 {Entry.PathFolder} 的 Java 存在异常")
  571. End If
  572. End Try
  573. End Sub)
  574. CheckThreads.Add(CheckThread)
  575. CheckThread.Start()
  576. Next
  577. '等待构造线程完成
  578. Wait:
  579. Thread.Sleep(10)
  580. For Each CheckThread In CheckThreads
  581. If CheckThread.IsAlive Then GoTo Wait
  582. Next
  583. Return Result
  584. End Function
  585. ''' <summary>
  586. ''' 模糊搜索指定文件夹下的 Java,并只进行粗略的检查。这不会搜索全部路径。
  587. ''' </summary>
  588. ''' <param name="OriginalPath">开始搜索的起始路径,不限制结尾。</param>
  589. ''' <param name="IsFullSearch">搜索当前文件夹下的全部文件夹(此参数不会传递到子文件夹)。</param>
  590. Private Sub JavaSearchFolder(OriginalPath As String, ByRef Results As Dictionary(Of String, Boolean), Source As Boolean, Optional IsFullSearch As Boolean = False)
  591. Try
  592. Logger.Info($"开始{If(IsFullSearch, "完全", "部分")}遍历查找:{OriginalPath}")
  593. JavaSearchFolder(DirectoryUtils.GetInfo(OriginalPath), Results, Source, IsFullSearch)
  594. Catch ex As UnauthorizedAccessException
  595. Logger.Info($"遍历查找 Java 时遭遇无权限的文件夹:{OriginalPath}")
  596. Catch ex As Exception
  597. Logger.Warn(ex, $"遍历查找 Java 时出错({OriginalPath})")
  598. End Try
  599. End Sub
  600. ''' <summary>
  601. ''' 模糊搜索指定文件夹下的 Java,并只进行粗略的检查。这不会搜索全部路径。
  602. ''' </summary>
  603. ''' <param name="OriginalPath">开始搜索的起始路径,不限制结尾。</param>
  604. ''' <param name="IsFullSearch">搜索当前文件夹下的全部文件夹(此参数不会传递到子文件夹)。</param>
  605. Private Sub JavaSearchFolder(OriginalPath As DirectoryInfo, ByRef Results As Dictionary(Of String, Boolean), Source As Boolean, Optional IsFullSearch As Boolean = False)
  606. Try
  607. '确认目录存在
  608. If Not OriginalPath.Exists Then Return
  609. Dim Folder As String = PathUtils.AddSlashSuffix(PathUtils.ForCompare(OriginalPath.FullName))
  610. '若该目录有 Java,则加入结果
  611. If FileUtils.Exists(Folder & "javaw.exe") Then Results(Folder) = Source
  612. '查找其下的所有文件夹
  613. '不应使用网易的 Java:https://github.com/Meloong-Git/PCL/issues/1279#issuecomment-2761489121
  614. Static Keywords As String() = {
  615. "java", "jdk", "jre", "env", "环境", "run", "软件", "mc", "dragon", "well", "bin", "sdk", "candidate", "current",
  616. "software", "cache", "temp", "corretto", "roaming", "users", "craft", "program", "世界", "net",
  617. "游戏", "oracle", "game", "file", "data", "jvm", "服务", "server", "客户", "client", "整合",
  618. "应用", "运行", "前置", "mojang", "官启", "官方", "新建文件夹", "eclipse", "microsoft", "hotspot",
  619. "runtime", "x86", "x64", "arm", "forge", "原版", "optifine", "官方", "启动", "hmcl", "mod", "forge", "fabric",
  620. "download", "launch", "程序", "path", "version", "baka", "pcl", "zulu", "local", "packages", "4297127d64ec6", "1.", "启动", "jbr"}
  621. For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories
  622. If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接
  623. Dim SearchEntry = PathUtils.GetLastPart(FolderInfo.Name).Lower '用于搜索的字符串
  624. If IsFullSearch OrElse
  625. OriginalPath.Name.Lower = "users" OrElse Val(SearchEntry) > 0 OrElse Keywords.Any(Function(w) SearchEntry.Contains(w)) OrElse SearchEntry = "bin" Then
  626. JavaSearchFolder(FolderInfo, Results, Source)
  627. End If
  628. Next
  629. Catch ex As UnauthorizedAccessException
  630. Logger.Trace(Function() $"遍历查找 Java 时遭遇无权限的文件夹({OriginalPath.FullName}):{ex.GetDisplay(False)}")
  631. Catch ex As Exception
  632. Logger.Warn(ex, $"遍历查找 Java 时出错({OriginalPath.FullName})")
  633. End Try
  634. End Sub
  635. #End Region
  636. #Region "下载"
  637. ''' <summary>
  638. ''' 获取下载 Java 的加载器。需要开启 IsForceRestart 以正常刷新 Java 列表。
  639. ''' </summary>
  640. Public Function GetJavaDownloadLoader() As LoaderCombo(Of String)
  641. Dim JavaDownloadLoader As New LoaderDownload("下载 Java 文件", New List(Of NetFile)) With {.ProgressWeight = 10}
  642. Dim Loader = New LoaderCombo(Of String)($"下载 Java", {
  643. New LoaderTask(Of String, List(Of NetFile))("获取 Java 下载信息", AddressOf JavaFileList) With {.ProgressWeight = 2},
  644. JavaDownloadLoader,
  645. JavaSearchLoader
  646. })
  647. AddHandler JavaDownloadLoader.OnStateChangedThread,
  648. Sub(Raw As LoaderBase, NewState As LoadState, OldState As LoadState)
  649. If (NewState = LoadState.Failed OrElse NewState = LoadState.Interrupted) AndAlso LastJavaBaseDir IsNot Nothing Then
  650. Logger.Warn($"由于下载未完成,清理未下载完成的 Java 文件:{LastJavaBaseDir}")
  651. DirectoryUtils.Delete(LastJavaBaseDir)
  652. ElseIf NewState = LoadState.Finished Then
  653. LastJavaBaseDir = Nothing
  654. End If
  655. End Sub
  656. JavaDownloadLoader.HasOnStateChangedThread = True
  657. Return Loader
  658. End Function
  659. Private LastJavaBaseDir As String = Nothing '用于在下载中断或失败时删除未完成下载的 Java 文件夹,防止残留只下了一半但 -version 能跑的 Java
  660. Private Sub JavaFileList(Loader As LoaderTask(Of String, List(Of NetFile)))
  661. Logger.Info("开始获取 Java 下载信息")
  662. Dim IndexFileStr As String = NetRequestByLoader(DlVersionListOrder(
  663. {"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"},
  664. {"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}
  665. ), IsJson:=True)
  666. '查找要下载的目标 Java
  667. Dim TargetEntry As JProperty = Nothing
  668. Dim Components As JObject = CType(GetJson(IndexFileStr), JObject)($"windows-x{If(Is32BitSystem, "86", "64")}")
  669. If Components.ContainsKey(Loader.Input) Then '精确匹配
  670. TargetEntry = Components.Property(Loader.Input)
  671. Else '模糊匹配
  672. TargetEntry = Components.Properties.FirstOrDefault(
  673. Function(c) c.Value IsNot Nothing AndAlso c.Value.ToArray.FirstOrDefault()?("version")("name").ToString.StartsWithF(Loader.Input))
  674. If TargetEntry Is Nothing Then Throw New Exception($"未能找到所需的 Java {Loader.Input}")
  675. End If
  676. Dim TargetComponent = TargetEntry.Value.ToArray.FirstOrDefault
  677. If TargetComponent Is Nothing Then Throw New Exception($"Mojang 未提供所需的 Java {Loader.Input}")
  678. '获取文件列表
  679. Dim Address As String = TargetComponent("manifest")("url")
  680. McLaunchLog($"准备下载 Java {TargetComponent("version")("name")}({TargetEntry.Name}):{Address}")
  681. Dim ListFileStr As String = NetRequestByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True)
  682. LastJavaBaseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Name & "\"
  683. Dim Results As New List(Of NetFile)
  684. For Each File As JProperty In CType(GetJson(ListFileStr), JObject)("files")
  685. If CType(File.Value, JObject)("downloads")?("raw") Is Nothing Then Continue For
  686. Dim Info As JObject = CType(File.Value, JObject)("downloads")("raw")
  687. Dim Checker As New FileChecker(ActualSize:=Info("size"), Hash:=Info("sha1"))
  688. If Checker.Hash = "12976a6c2b227cbac58969c1455444596c894656" OrElse Checker.Hash = "c80e4bab46e34d02826eab226a4441d0970f2aba" OrElse Checker.Hash = "84d2102ad171863db04e7ee22a259d1f6c5de4a5" Then
  689. '跳过 3 个无意义大量重复文件(#3827)
  690. Continue For
  691. End If
  692. If Checker.Check(LastJavaBaseDir & File.Name) Is Nothing Then Continue For '跳过已存在的文件
  693. Dim Url As String = Info("url")
  694. Results.Add(New NetFile(DlSourceOrder({Url}, {Url.Replace("piston-data.mojang.com", "bmclapi2.bangbang93.com")}), LastJavaBaseDir & File.Name, Checker))
  695. Next
  696. Loader.Output = Results
  697. Logger.Info($"需要下载 {Results.Count} 个文件,目标文件夹:{LastJavaBaseDir}")
  698. End Sub
  699. #End Region
  700. End Module