1use std::path::{Path, PathBuf};
3
4use inner_future::stream::StreamExt;
5
6use crate::{prelude::*, utils::Arch};
7
8pub struct JavaRuntime {
10 java_path: String,
11 java_version: String,
12 java_main_version: u8,
13 java_64bit: bool,
14 java_arch: Arch,
15}
16
17impl JavaRuntime {
18 pub async fn from_java_path(java_path: impl AsRef<std::ffi::OsStr>) -> DynResult<Self> {
22 let output = query_java_version_output(&java_path).await?;
23 let version = query_java_version(&output);
24 let java_main_version = get_java_version(version);
25 let java_64bit = query_java_is_64bit(&output);
26 let java_arch = {
27 #[cfg(target_os = "macos")]
28 {
29 crate::utils::get_exec_arch(std::path::PathBuf::from(java_path.as_ref())).await?
30 }
31 #[cfg(target_os = "windows")]
32 {
33 if java_64bit {
35 Arch::X64
36 } else {
37 Arch::X86
38 }
39 }
40 #[cfg(target_os = "linux")]
41 {
42 #[cfg(target_arch = "i686")]
43 {
44 Arch::X86
45 }
46 #[cfg(target_arch = "x86_64")]
47 {
48 Arch::X64
49 }
50 #[cfg(target_arch = "aarch64")]
51 {
52 Arch::ARM64
53 }
54 }
55 };
56 Ok(Self {
57 java_path: java_path.as_ref().to_string_lossy().to_string(),
58 java_64bit,
59 java_version: version.to_owned(),
60 java_main_version,
61 java_arch,
62 })
63 }
64
65 #[inline]
67 pub fn path(&self) -> &str {
68 &self.java_path
69 }
70
71 #[inline]
73 pub fn version(&self) -> &str {
74 &self.java_version
75 }
76
77 #[inline]
79 pub fn is_64bit(&self) -> bool {
80 self.java_64bit
81 }
82
83 #[inline]
85 pub fn main_version(&self) -> u8 {
86 self.java_main_version
87 }
88
89 #[inline]
91 pub fn arch(&self) -> Arch {
92 self.java_arch
93 }
94}
95
96async fn query_java_version_output(java_path: impl AsRef<std::ffi::OsStr>) -> DynResult<String> {
98 let c = {
99 #[cfg(windows)]
100 {
101 use inner_future::process::windows::CommandExt;
102 inner_future::process::Command::new(java_path)
103 .arg("-version")
104 .stdout(std::process::Stdio::piped())
105 .stderr(std::process::Stdio::piped())
106 .creation_flags(0x00000200 | 0x08000000)
107 .spawn()?
108 }
109 #[cfg(not(windows))]
110 {
111 inner_future::process::Command::new(java_path)
112 .arg("-version")
113 .stdout(std::process::Stdio::piped())
114 .stderr(std::process::Stdio::piped())
115 .spawn()?
116 }
117 };
118 let output = c.output().await?;
119 Ok(String::from_utf8(output.stderr)?)
120}
121
122fn query_java_is_64bit(java_output: &str) -> bool {
124 java_output.contains("64-Bit")
125}
126
127fn get_java_version(java_version_string: &str) -> u8 {
133 fn parser(input: &str) -> nom::IResult<&str, &str> {
134 nom::character::complete::digit1(input)
135 }
136 if let Ok((_, r)) = parser(java_version_string) {
137 let r = r.parse().unwrap(); if r > 1 {
139 r
140 } else if java_version_string.contains("1.8") {
141 8
142 } else if java_version_string.contains("1.7") {
143 7
144 } else {
145 0 }
147 } else if java_version_string.contains("1.8") {
148 8
149 } else if java_version_string.contains("1.7") {
150 7
151 } else {
152 0 }
154}
155
156pub async fn search_for_java() -> Vec<String> {
159 async fn check_bin_java_directory(path: impl AsRef<Path>, result: &mut Vec<String>) {
161 println!("Searching {}", path.as_ref().to_string_lossy());
162 if let Ok(mut d) = inner_future::fs::read_dir(path).await {
163 while let Ok(Some(d)) = d.try_next().await {
164 let mut path = d.path();
165 #[cfg(target_os = "macos")]
166 path.push("Contents/Home");
167 path.push("bin");
168 #[cfg(windows)]
169 {
170 path.push("java.exe");
171 if path.is_file() {
172 result.push(d.path().to_string_lossy().to_string());
173 }
174 {
175 path.pop();
176 path.push("javaw.exe");
177 if path.is_file() {
178 result.push(path.to_string_lossy().to_string());
179 }
180 }
181 }
182 #[cfg(not(windows))]
183 {
184 path.push("java");
185 if path.is_file() {
186 result.push(path.to_string_lossy().to_string());
187 }
188 }
189 }
190 }
191 }
192
193 fn check_java(path: impl Into<PathBuf>, result: &mut Vec<String>) {
194 let mut path: PathBuf = path.into();
195 #[cfg(windows)]
196 {
197 path.push("java.exe");
198 if path.is_file() {
199 result.push(path.to_string_lossy().to_string());
200 }
201 path.pop();
202 path.push("javaw.exe");
203 if path.is_file() {
204 result.push(path.to_string_lossy().to_string());
205 }
206 }
207 #[cfg(not(windows))]
208 {
209 path.push("java");
210 if path.is_file() {
211 result.push(path.to_string_lossy().to_string());
212 }
213 }
214 }
215 #[cfg(windows)]
216 {
217 let mut result = Vec::with_capacity(16);
218 use winreg::{enums::*, *};
220 fn search_local_machine_reg_value(key: &str, result: &mut Vec<String>) {
221 let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
222 if let Ok(subkey) = hklm
223 .open_subkey("SOFTWARE\\JavaSoft")
224 .and_then(|subkey| subkey.open_subkey(key))
225 {
226 for s in subkey.enum_keys().flatten() {
227 let subkey = subkey.open_subkey(s);
228 if let Ok(subkey) = subkey {
229 if let Ok(v) = subkey.get_value("JavaHome") {
230 result.push(v);
231 }
232 }
233 }
234 }
235 }
236 search_local_machine_reg_value("Java Runtime Environment", &mut result);
237 search_local_machine_reg_value("Java Development Kit", &mut result);
238 search_local_machine_reg_value("JRE", &mut result);
239 search_local_machine_reg_value("JDK", &mut result);
240 async fn list_java_directory(path: &str, result: &mut Vec<String>) {
241 let mut path = std::path::PathBuf::from(path);
242 path.push("Java");
243 check_bin_java_directory(&path, result).await;
244 path.pop();
245 path.push("BellSoft");
246 check_bin_java_directory(&path, result).await;
247 path.pop();
248 path.push("AdoptOpenJDK");
249 check_bin_java_directory(&path, result).await;
250 path.pop();
251 path.push("Zulu");
252 check_bin_java_directory(&path, result).await;
253 path.pop();
254 path.push("Microsoft");
255 check_bin_java_directory(&path, result).await;
256 path.pop();
257 path.push("Eclipse Foundation");
258 check_bin_java_directory(&path, result).await;
259 path.pop();
260 path.push("Semeru");
261 check_bin_java_directory(&path, result).await;
262 path.pop();
263 }
264 if let Ok(program_files) = std::env::var("ProgramFiles")
265 .or_else::<std::env::VarError, _>(|_| Ok("C:\\Program Files".into()))
266 {
267 list_java_directory(&program_files, &mut result).await;
268 }
269 if let Ok(program_files) = std::env::var("ProgramFiles(x86)")
270 .or_else::<std::env::VarError, _>(|_| Ok("C:\\Program Files (x86)".into()))
271 {
272 list_java_directory(&program_files, &mut result).await;
273 let mut minecraft_launcher_dir = PathBuf::from(program_files);
274 minecraft_launcher_dir
275 .push("Minecraft Launcher\\runtime\\jre-legacy\\windows-x64\\jre-legacy\\bin");
276 check_java(&minecraft_launcher_dir, &mut result);
277 minecraft_launcher_dir.pop();
278 minecraft_launcher_dir.pop();
279 minecraft_launcher_dir.pop();
280 minecraft_launcher_dir.push("windows-x86\\jre-legacy\\bin");
281 check_java(&minecraft_launcher_dir, &mut result);
282 minecraft_launcher_dir.pop();
283 minecraft_launcher_dir.pop();
284 minecraft_launcher_dir.pop();
285 minecraft_launcher_dir.pop();
286 minecraft_launcher_dir.push("java-runtime-alpha\\windows-x64\\java-runtime-alpha\\bin");
287 check_java(&minecraft_launcher_dir, &mut result);
288 minecraft_launcher_dir.pop();
289 minecraft_launcher_dir.pop();
290 minecraft_launcher_dir.pop();
291 minecraft_launcher_dir.push("windows-x86\\java-runtime-alpha\\bin");
292 check_java(&minecraft_launcher_dir, &mut result);
293 }
294 if let Ok(program_files) = std::env::var("ProgramFiles(ARM)")
295 .or_else::<std::env::VarError, _>(|_| Ok("C:\\Program Files (ARM)".into()))
296 {
297 list_java_directory(&program_files, &mut result).await;
298 }
299 if let Ok(paths) = std::env::var("PATH") {
301 for path in paths.split(';') {
302 check_java(path, &mut result);
303 }
304 }
305 if let Ok(path) = std::env::var("JABBA_HOME") {
307 let path = PathBuf::from(path).join("jdk");
308 check_bin_java_directory(path.as_path(), &mut result).await;
309 }
310 let mut result: Vec<_> = result
311 .into_iter()
312 .map(|mut a| {
313 #[cfg(windows)]
314 {
315 if !a.ends_with("java.exe")
316 && !a.ends_with("javaw.exe")
317 && !a.ends_with("java")
318 && !a.ends_with("javaw")
319 {
320 if !a.ends_with('\\') && !a.ends_with('/') {
321 a.push('\\');
322 }
323 a.push_str("bin\\java.exe");
324 }
325 }
326 #[cfg(not(windows))]
327 {
328 if !a.ends_with("java") {
329 if !a.ends_with('/') {
330 a.push('/');
331 }
332 a.push_str("bin/java");
333 }
334 }
335 a
336 })
337 .collect();
338 result.sort();
339 result.dedup();
340 result
341 }
342 #[cfg(target_os = "linux")]
343 {
344 let mut result = Vec::with_capacity(16);
345
346 check_bin_java_directory("/usr/java/bin", &mut result).await;
348 check_bin_java_directory("/usr/lib/jvm/bin", &mut result).await;
349 check_bin_java_directory("/usr/lib32/jvm/bin", &mut result).await;
350
351 if let Ok(paths) = std::env::var("PATH") {
353 for path in paths.split(';') {
354 check_java(path, &mut result);
355 }
356 }
357 if let Ok(path) = std::env::var("JABBA_HOME") {
359 let path = PathBuf::from(path).join("jdk");
360 check_bin_java_directory(path.as_path(), &mut result).await;
361 }
362 if let Some(mut home_path) = dirs::home_dir() {
364 home_path.push(".minecraft/runtime/jre-legacy/linux/jre-legacy/bin");
365 check_java(&home_path, &mut result);
366 home_path.push("../../../../java-runtime-alpha/linux/java-runtime-alpha/bin");
367 check_java(&home_path, &mut result);
368 }
369
370 result
371 }
372 #[cfg(target_os = "macos")]
373 {
374 let mut result = Vec::with_capacity(16);
375
376 check_bin_java_directory("/Library/Java/JavaVirtualMachines", &mut result).await;
377 check_bin_java_directory("/System/Library/Java/JavaVirtualMachines", &mut result).await;
378 check_java(
379 "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java",
380 &mut result,
381 );
382 check_java("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java", &mut result);
383
384 result
385 }
386}
387
388fn query_java_version(java_output: &str) -> &str {
390 fn parser(input: &str) -> nom::IResult<&str, &str> {
391 nom::bytes::complete::take_until("\"")(input)
392 }
393 let pat = "version \"";
394 if let Some(p) = java_output.find(pat) {
395 if let Ok((_, r)) = parser(&java_output[p + pat.len()..]) {
396 r
397 } else {
398 ""
399 }
400 } else {
401 ""
402 }
403}