ModJava.vb 40 KB

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