You've already forked AstralRinth
v0.10.302 #2
Generated
+1
@@ -8889,6 +8889,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"fs4",
|
||||
"futures",
|
||||
"hashlink",
|
||||
"hickory-resolver",
|
||||
"indicatif",
|
||||
"notify",
|
||||
|
||||
@@ -60,6 +60,7 @@ flate2 = "1.1.2"
|
||||
fs4 = { version = "0.13.1", default-features = false }
|
||||
futures = { version = "0.3.31", default-features = false }
|
||||
futures-util = "0.3.31"
|
||||
hashlink = "0.10.0"
|
||||
hex = "0.4.3"
|
||||
hickory-resolver = "0.25.2"
|
||||
hmac = "0.12.1"
|
||||
|
||||
@@ -127,7 +127,7 @@ async function handleJavaFileInput() {
|
||||
const filePath = await open()
|
||||
|
||||
if (filePath) {
|
||||
let result = await get_jre(filePath.path ?? filePath)
|
||||
let result = await get_jre(filePath.path ?? filePath).catch(handleError)
|
||||
if (!result) {
|
||||
result = {
|
||||
path: filePath.path ?? filePath,
|
||||
|
||||
@@ -41,8 +41,8 @@ pub async fn jre_find_filtered_jres(
|
||||
// Validates JRE at a given path
|
||||
// Returns None if the path is not a valid JRE
|
||||
#[tauri::command]
|
||||
pub async fn jre_get_jre(path: PathBuf) -> Result<Option<JavaVersion>> {
|
||||
jre::check_jre(path).await.map_err(|e| e.into())
|
||||
pub async fn jre_get_jre(path: PathBuf) -> Result<JavaVersion> {
|
||||
Ok(jre::check_jre(path).await?)
|
||||
}
|
||||
|
||||
// Tests JRE of a certain version
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"linux": {
|
||||
"deb": {
|
||||
|
||||
@@ -23,6 +23,7 @@ quick-xml = { workspace = true, features = ["async-tokio"] }
|
||||
enumset.workspace = true
|
||||
chardetng.workspace = true
|
||||
encoding_rs.workspace = true
|
||||
hashlink.workspace = true
|
||||
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
daedalus.workspace = true
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
public final class JavaInfo {
|
||||
private static final String[] CHECKED_PROPERTIES = new String[] {
|
||||
"os.arch",
|
||||
"java.version"
|
||||
};
|
||||
private static final String[] CHECKED_PROPERTIES = new String[] {
|
||||
"os.arch",
|
||||
"java.version"
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
int returnCode = 0;
|
||||
public static void main(String[] args) {
|
||||
int returnCode = 0;
|
||||
|
||||
for (String key : CHECKED_PROPERTIES) {
|
||||
String property = System.getProperty(key);
|
||||
for (String key : CHECKED_PROPERTIES) {
|
||||
String property = System.getProperty(key);
|
||||
|
||||
if (property != null) {
|
||||
System.out.println(key + "=" + property);
|
||||
} else {
|
||||
returnCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
System.exit(returnCode);
|
||||
if (property != null) {
|
||||
System.out.println(key + "=" + property);
|
||||
} else {
|
||||
returnCode = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.exit(returnCode);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,118 @@
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class MinecraftLaunch {
|
||||
public static void main(String[] args) throws IOException, ReflectiveOperationException {
|
||||
final String mainClass = args[0];
|
||||
final String[] gameArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
|
||||
System.setProperty("modrinth.process.args", String.join("\u001f", gameArgs));
|
||||
parseInput();
|
||||
|
||||
relaunch(mainClass, gameArgs);
|
||||
}
|
||||
|
||||
private static void parseInput() throws IOException {
|
||||
final ByteArrayOutputStream line = new ByteArrayOutputStream();
|
||||
while (true) {
|
||||
final int b = System.in.read();
|
||||
if (b < 0) {
|
||||
throw new IllegalStateException("Stdin terminated while parsing");
|
||||
}
|
||||
if (b != '\n') {
|
||||
line.write(b);
|
||||
continue;
|
||||
}
|
||||
if (handleLine(line.toString("UTF-8"))) {
|
||||
break;
|
||||
}
|
||||
line.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean handleLine(String line) {
|
||||
final String[] parts = line.split("\t", 2);
|
||||
switch (parts[0]) {
|
||||
case "property": {
|
||||
final String[] keyValue = parts[1].split("\t", 2);
|
||||
System.setProperty(keyValue[0], keyValue[1]);
|
||||
return false;
|
||||
}
|
||||
case "launch":
|
||||
return true;
|
||||
}
|
||||
|
||||
System.err.println("Unknown input line " + line);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void relaunch(String mainClassName, String[] args) throws ReflectiveOperationException {
|
||||
final int javaVersion = getJavaVersion();
|
||||
final Class<?> mainClass = Class.forName(mainClassName);
|
||||
|
||||
if (javaVersion >= 25) {
|
||||
Method mainMethod;
|
||||
try {
|
||||
mainMethod = findMainMethodJep512(mainClass);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
System.err
|
||||
.println("[MODRINTH] Unable to call JDK findMainMethod. Falling back to pre-Java 25 main method finding.");
|
||||
// If the above fails due to JDK implementation details changing
|
||||
try {
|
||||
mainMethod = findSimpleMainMethod(mainClass);
|
||||
} catch (ReflectiveOperationException innerE) {
|
||||
e.addSuppressed(innerE);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (mainMethod == null) {
|
||||
throw new IllegalArgumentException("Could not find main() method");
|
||||
}
|
||||
|
||||
Object thisObject = null;
|
||||
if (!Modifier.isStatic(mainMethod.getModifiers())) {
|
||||
thisObject = mainClass.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
final Object[] parameters = mainMethod.getParameterCount() > 0
|
||||
? new Object[] { args }
|
||||
: new Object[] {};
|
||||
|
||||
mainMethod.invoke(thisObject, parameters);
|
||||
} else {
|
||||
findSimpleMainMethod(mainClass).invoke(null, new Object[] { args });
|
||||
}
|
||||
}
|
||||
|
||||
private static int getJavaVersion() {
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
|
||||
final int dotIndex = javaVersion.indexOf('.');
|
||||
if (dotIndex != -1) {
|
||||
javaVersion = javaVersion.substring(0, dotIndex);
|
||||
}
|
||||
|
||||
final int dashIndex = javaVersion.indexOf('-');
|
||||
if (dashIndex != -1) {
|
||||
javaVersion = javaVersion.substring(0, dashIndex);
|
||||
}
|
||||
|
||||
return Integer.parseInt(javaVersion);
|
||||
}
|
||||
|
||||
private static Method findMainMethodJep512(Class<?> mainClass) throws ReflectiveOperationException {
|
||||
// BEWARE BELOW: This code may break if JDK internals to find the main method
|
||||
// change.
|
||||
final Class<?> methodFinderClass = Class.forName("jdk.internal.misc.MethodFinder");
|
||||
final Method methodFinderMethod = methodFinderClass.getDeclaredMethod("findMainMethod", Class.class);
|
||||
final Object result = methodFinderMethod.invoke(null, mainClass);
|
||||
return (Method) result;
|
||||
}
|
||||
|
||||
private static Method findSimpleMainMethod(Class<?> mainClass) throws NoSuchMethodException {
|
||||
return mainClass.getMethod("main", String[].class);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use std::path::PathBuf;
|
||||
use sysinfo::{MemoryRefreshKind, RefreshKind};
|
||||
|
||||
use crate::util::io;
|
||||
use crate::util::jre::extract_java_majorminor_version;
|
||||
use crate::util::jre::extract_java_version;
|
||||
use crate::{
|
||||
LoadingBarType, State,
|
||||
util::jre::{self},
|
||||
@@ -38,9 +38,9 @@ pub async fn find_filtered_jres(
|
||||
Ok(if let Some(java_version) = java_version {
|
||||
jres.into_iter()
|
||||
.filter(|jre| {
|
||||
let jre_version = extract_java_majorminor_version(&jre.version);
|
||||
let jre_version = extract_java_version(&jre.version);
|
||||
if let Ok(jre_version) = jre_version {
|
||||
jre_version.1 == java_version
|
||||
jre_version == java_version
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -157,8 +157,8 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
||||
}
|
||||
|
||||
// Validates JRE at a given at a given path
|
||||
pub async fn check_jre(path: PathBuf) -> crate::Result<Option<JavaVersion>> {
|
||||
Ok(jre::check_java_at_filepath(&path).await)
|
||||
pub async fn check_jre(path: PathBuf) -> crate::Result<JavaVersion> {
|
||||
jre::check_java_at_filepath(&path).await
|
||||
}
|
||||
|
||||
// Test JRE at a given path
|
||||
@@ -166,11 +166,11 @@ pub async fn test_jre(
|
||||
path: PathBuf,
|
||||
major_version: u32,
|
||||
) -> crate::Result<bool> {
|
||||
let Some(jre) = jre::check_java_at_filepath(&path).await else {
|
||||
let Ok(jre) = jre::check_java_at_filepath(&path).await else {
|
||||
return Ok(false);
|
||||
};
|
||||
let (major, _) = extract_java_majorminor_version(&jre.version)?;
|
||||
Ok(major == major_version)
|
||||
let version = extract_java_version(&jre.version)?;
|
||||
Ok(version == major_version)
|
||||
}
|
||||
|
||||
// Gets maximum memory in KiB.
|
||||
|
||||
@@ -13,7 +13,7 @@ use daedalus::{
|
||||
modded::SidedDataEntry,
|
||||
};
|
||||
use dunce::canonicalize;
|
||||
use std::collections::HashSet;
|
||||
use hashlink::LinkedHashSet;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
use uuid::Uuid;
|
||||
@@ -24,7 +24,7 @@ const TEMPORARY_REPLACE_CHAR: &str = "\n";
|
||||
pub fn get_class_paths(
|
||||
libraries_path: &Path,
|
||||
libraries: &[Library],
|
||||
client_path: &Path,
|
||||
launcher_class_path: &[&Path],
|
||||
java_arch: &str,
|
||||
minecraft_updated: bool,
|
||||
) -> crate::Result<String> {
|
||||
@@ -48,20 +48,22 @@ pub fn get_class_paths(
|
||||
|
||||
Some(get_lib_path(libraries_path, &library.name, false))
|
||||
})
|
||||
.collect::<Result<HashSet<_>, _>>()?;
|
||||
.collect::<Result<LinkedHashSet<_>, _>>()?;
|
||||
|
||||
cps.insert(
|
||||
canonicalize(client_path)
|
||||
.map_err(|_| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Specified class path {} does not exist",
|
||||
client_path.to_string_lossy()
|
||||
))
|
||||
.as_error()
|
||||
})?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
for launcher_path in launcher_class_path {
|
||||
cps.insert(
|
||||
canonicalize(launcher_path)
|
||||
.map_err(|_| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Specified class path {} does not exist",
|
||||
launcher_path.to_string_lossy()
|
||||
))
|
||||
.as_error()
|
||||
})?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(cps
|
||||
.into_iter()
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::state::{
|
||||
Credentials, JavaVersion, ProcessMetadata, ProfileInstallStage,
|
||||
};
|
||||
use crate::util::io;
|
||||
use crate::{State, process, state as st};
|
||||
use crate::{State, get_resource_file, process, state as st};
|
||||
use chrono::Utc;
|
||||
use daedalus as d;
|
||||
use daedalus::minecraft::{LoggingSide, RuleAction, VersionInfo};
|
||||
@@ -19,6 +19,7 @@ use serde::Deserialize;
|
||||
use st::Profile;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::process::Command;
|
||||
|
||||
mod args;
|
||||
@@ -124,12 +125,10 @@ pub async fn get_java_version_from_profile(
|
||||
version_info: &VersionInfo,
|
||||
) -> crate::Result<Option<JavaVersion>> {
|
||||
if let Some(java) = profile.java_path.as_ref() {
|
||||
let java = crate::api::jre::check_jre(std::path::PathBuf::from(java))
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let java =
|
||||
crate::api::jre::check_jre(std::path::PathBuf::from(java)).await;
|
||||
|
||||
if let Some(java) = java {
|
||||
if let Ok(java) = java {
|
||||
return Ok(Some(java));
|
||||
}
|
||||
}
|
||||
@@ -289,13 +288,7 @@ pub async fn install_minecraft(
|
||||
};
|
||||
|
||||
// Test jre version
|
||||
let java_version = crate::api::jre::check_jre(java_version.clone())
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Java path invalid or non-functional: {java_version:?}"
|
||||
))
|
||||
})?;
|
||||
let java_version = crate::api::jre::check_jre(java_version.clone()).await?;
|
||||
|
||||
if set_java {
|
||||
java_version.upsert(&state.pool).await?;
|
||||
@@ -560,14 +553,7 @@ pub async fn launch_minecraft(
|
||||
|
||||
// Test jre version
|
||||
let java_version =
|
||||
crate::api::jre::check_jre(java_version.path.clone().into())
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Java path invalid or non-functional: {}",
|
||||
java_version.path
|
||||
))
|
||||
})?;
|
||||
crate::api::jre::check_jre(java_version.path.clone().into()).await?;
|
||||
|
||||
let client_path = state
|
||||
.directories
|
||||
@@ -603,33 +589,43 @@ pub async fn launch_minecraft(
|
||||
io::create_dir_all(&natives_dir).await?;
|
||||
}
|
||||
|
||||
command
|
||||
.args(
|
||||
args::get_jvm_arguments(
|
||||
args.get(&d::minecraft::ArgumentType::Jvm)
|
||||
.map(|x| x.as_slice()),
|
||||
&natives_dir,
|
||||
let (main_class_keep_alive, main_class_path) =
|
||||
get_resource_file!("../../library" / "MinecraftLaunch.class")?;
|
||||
|
||||
command.args(
|
||||
args::get_jvm_arguments(
|
||||
args.get(&d::minecraft::ArgumentType::Jvm)
|
||||
.map(|x| x.as_slice()),
|
||||
&natives_dir,
|
||||
&state.directories.libraries_dir(),
|
||||
&state.directories.log_configs_dir(),
|
||||
&args::get_class_paths(
|
||||
&state.directories.libraries_dir(),
|
||||
&state.directories.log_configs_dir(),
|
||||
&args::get_class_paths(
|
||||
&state.directories.libraries_dir(),
|
||||
version_info.libraries.as_slice(),
|
||||
&client_path,
|
||||
&java_version.architecture,
|
||||
minecraft_updated,
|
||||
)?,
|
||||
&version_jar,
|
||||
*memory,
|
||||
Vec::from(java_args),
|
||||
version_info.libraries.as_slice(),
|
||||
&[main_class_path.parent().unwrap(), &client_path],
|
||||
&java_version.architecture,
|
||||
quick_play_type,
|
||||
version_info
|
||||
.logging
|
||||
.as_ref()
|
||||
.and_then(|x| x.get(&LoggingSide::Client)),
|
||||
)?
|
||||
.into_iter(),
|
||||
)
|
||||
minecraft_updated,
|
||||
)?,
|
||||
&version_jar,
|
||||
*memory,
|
||||
Vec::from(java_args),
|
||||
&java_version.architecture,
|
||||
quick_play_type,
|
||||
version_info
|
||||
.logging
|
||||
.as_ref()
|
||||
.and_then(|x| x.get(&LoggingSide::Client)),
|
||||
)?
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
// The java launcher code requires internal JDK code in Java 25+ in order to support JEP 512
|
||||
if java_version.parsed_version >= 25 {
|
||||
command.arg("--add-opens=jdk.internal/jdk.internal.misc=ALL-UNNAMED");
|
||||
}
|
||||
|
||||
command
|
||||
.arg("MinecraftLaunch")
|
||||
.arg(version_info.main_class.clone())
|
||||
.args(
|
||||
args::get_minecraft_arguments(
|
||||
@@ -744,6 +740,40 @@ pub async fn launch_minecraft(
|
||||
post_exit_hook,
|
||||
state.directories.profile_logs_dir(&profile.path),
|
||||
version_info.logging.is_some(),
|
||||
main_class_keep_alive,
|
||||
async |process: &ProcessMetadata, stdin| {
|
||||
let process_start_time = process.start_time.to_rfc3339();
|
||||
let profile_created_time = profile.created.to_rfc3339();
|
||||
let profile_modified_time = profile.modified.to_rfc3339();
|
||||
let system_properties = [
|
||||
("modrinth.process.startTime", Some(&process_start_time)),
|
||||
("modrinth.profile.created", Some(&profile_created_time)),
|
||||
("modrinth.profile.icon", profile.icon_path.as_ref()),
|
||||
(
|
||||
"modrinth.profile.link.project",
|
||||
profile.linked_data.as_ref().map(|x| &x.project_id),
|
||||
),
|
||||
(
|
||||
"modrinth.profile.link.version",
|
||||
profile.linked_data.as_ref().map(|x| &x.version_id),
|
||||
),
|
||||
("modrinth.profile.modified", Some(&profile_modified_time)),
|
||||
("modrinth.profile.name", Some(&profile.name)),
|
||||
];
|
||||
for (key, value) in system_properties {
|
||||
let Some(value) = value else {
|
||||
continue;
|
||||
};
|
||||
stdin.write_all(b"property\t").await?;
|
||||
stdin.write_all(key.as_bytes()).await?;
|
||||
stdin.write_u8(b'\t').await?;
|
||||
stdin.write_all(value.as_bytes()).await?;
|
||||
stdin.write_u8(b'\n').await?;
|
||||
}
|
||||
stdin.write_all(b"launch\n").await?;
|
||||
stdin.flush().await?;
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Clone)]
|
||||
pub struct JavaVersion {
|
||||
pub major_version: u32,
|
||||
pub parsed_version: u32,
|
||||
pub version: String,
|
||||
pub architecture: String,
|
||||
pub path: String,
|
||||
@@ -30,7 +30,7 @@ impl JavaVersion {
|
||||
.await?;
|
||||
|
||||
Ok(res.map(|x| JavaVersion {
|
||||
major_version,
|
||||
parsed_version: major_version,
|
||||
version: x.full_version,
|
||||
architecture: x.architecture,
|
||||
path: x.path,
|
||||
@@ -52,7 +52,7 @@ impl JavaVersion {
|
||||
acc.insert(
|
||||
x.major_version as u32,
|
||||
JavaVersion {
|
||||
major_version: x.major_version as u32,
|
||||
parsed_version: x.major_version as u32,
|
||||
version: x.full_version,
|
||||
architecture: x.architecture,
|
||||
path: x.path,
|
||||
@@ -70,7 +70,7 @@ impl JavaVersion {
|
||||
&self,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<()> {
|
||||
let major_version = self.major_version as i32;
|
||||
let major_version = self.parsed_version as i32;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
|
||||
@@ -83,7 +83,7 @@ where
|
||||
settings.prev_custom_dir = Some(old_launcher_root_str.clone());
|
||||
|
||||
for (_, legacy_version) in legacy_settings.java_globals.0 {
|
||||
if let Ok(Some(java_version)) =
|
||||
if let Ok(java_version) =
|
||||
check_jre(PathBuf::from(legacy_version.path)).await
|
||||
{
|
||||
java_version.upsert(exec).await?;
|
||||
|
||||
@@ -8,12 +8,14 @@ use quick_xml::Reader;
|
||||
use quick_xml::events::Event;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitStatus;
|
||||
use tempfile::TempDir;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio::process::{Child, ChildStdin, Command};
|
||||
use uuid::Uuid;
|
||||
|
||||
const LAUNCHER_LOG_PATH: &str = "launcher_log.txt";
|
||||
@@ -35,6 +37,7 @@ impl ProcessManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn insert_new_process(
|
||||
&self,
|
||||
profile_path: &str,
|
||||
@@ -42,24 +45,42 @@ impl ProcessManager {
|
||||
post_exit_command: Option<String>,
|
||||
logs_folder: PathBuf,
|
||||
xml_logging: bool,
|
||||
main_class_keep_alive: TempDir,
|
||||
post_process_init: impl AsyncFnOnce(
|
||||
&ProcessMetadata,
|
||||
&mut ChildStdin,
|
||||
) -> crate::Result<()>,
|
||||
) -> crate::Result<ProcessMetadata> {
|
||||
mc_command.stdout(std::process::Stdio::piped());
|
||||
mc_command.stderr(std::process::Stdio::piped());
|
||||
mc_command.stdin(std::process::Stdio::piped());
|
||||
|
||||
let mut mc_proc = mc_command.spawn().map_err(IOError::from)?;
|
||||
|
||||
let stdout = mc_proc.stdout.take();
|
||||
let stderr = mc_proc.stderr.take();
|
||||
|
||||
let process = Process {
|
||||
let mut process = Process {
|
||||
metadata: ProcessMetadata {
|
||||
uuid: Uuid::new_v4(),
|
||||
start_time: Utc::now(),
|
||||
profile_path: profile_path.to_string(),
|
||||
},
|
||||
child: mc_proc,
|
||||
_main_class_keep_alive: main_class_keep_alive,
|
||||
};
|
||||
|
||||
if let Err(e) = post_process_init(
|
||||
&process.metadata,
|
||||
&mut process.child.stdin.as_mut().unwrap(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to run post-process init: {e}");
|
||||
let _ = process.child.kill().await;
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
let metadata = process.metadata.clone();
|
||||
|
||||
if !logs_folder.exists() {
|
||||
@@ -193,6 +214,7 @@ pub struct ProcessMetadata {
|
||||
struct Process {
|
||||
metadata: ProcessMetadata,
|
||||
child: Child,
|
||||
_main_class_keep_alive: TempDir,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::io::Error.
|
||||
|
||||
use std::{io::Write, path::Path};
|
||||
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
@@ -299,3 +298,36 @@ pub async fn metadata(
|
||||
path: path.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets a resource file from the executable. Returns `theseus::Result<(TempDir, PathBuf)>`.
|
||||
#[macro_export]
|
||||
macro_rules! get_resource_file {
|
||||
($relative_dir:literal / $file_name:literal) => {
|
||||
'get_resource_file: {
|
||||
let dir = match tempfile::tempdir() {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
break 'get_resource_file $crate::Result::Err(
|
||||
$crate::util::io::IOError::from(e).into(),
|
||||
);
|
||||
}
|
||||
};
|
||||
let path = dir.path().join($file_name);
|
||||
if let Err(e) = $crate::util::io::write(
|
||||
&path,
|
||||
include_bytes!(concat!($relative_dir, "/", $file_name)),
|
||||
)
|
||||
.await
|
||||
{
|
||||
break 'get_resource_file $crate::Result::Err(e.into());
|
||||
}
|
||||
let path = match $crate::util::io::canonicalize(path) {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
break 'get_resource_file $crate::Result::Err(e.into());
|
||||
}
|
||||
};
|
||||
$crate::Result::Ok((dir, path))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::process::Command;
|
||||
use std::{collections::HashSet, path::Path};
|
||||
use tokio::task::JoinError;
|
||||
|
||||
use crate::State;
|
||||
use crate::{State, get_resource_file};
|
||||
#[cfg(target_os = "windows")]
|
||||
use winreg::{
|
||||
RegKey,
|
||||
@@ -183,7 +183,6 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
|
||||
// Gets all JREs from the PATH env variable
|
||||
#[tracing::instrument]
|
||||
|
||||
async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
|
||||
{
|
||||
Box::pin(async move {
|
||||
@@ -239,54 +238,49 @@ pub const JAVA_BIN: &str = if cfg!(target_os = "windows") {
|
||||
pub async fn check_java_at_filepaths(
|
||||
paths: HashSet<PathBuf>,
|
||||
) -> HashSet<JavaVersion> {
|
||||
let jres = stream::iter(paths.into_iter())
|
||||
stream::iter(paths.into_iter())
|
||||
.map(|p: PathBuf| {
|
||||
tokio::task::spawn(async move { check_java_at_filepath(&p).await })
|
||||
})
|
||||
.buffer_unordered(64)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
jres.into_iter().filter_map(|x| x.ok()).flatten().collect()
|
||||
.filter_map(async |x| x.ok().and_then(Result::ok))
|
||||
.collect()
|
||||
.await
|
||||
}
|
||||
|
||||
// For example filepath 'path', attempt to resolve it and get a Java version at this path
|
||||
// If no such path exists, or no such valid java at this path exists, returns None
|
||||
#[tracing::instrument]
|
||||
|
||||
pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
|
||||
pub async fn check_java_at_filepath(path: &Path) -> crate::Result<JavaVersion> {
|
||||
// Attempt to canonicalize the potential java filepath
|
||||
// If it fails, this path does not exist and None is returned (no Java here)
|
||||
let Ok(path) = io::canonicalize(path) else {
|
||||
return None;
|
||||
};
|
||||
let path = io::canonicalize(path)?;
|
||||
|
||||
// Checks for existence of Java at this filepath
|
||||
// Adds JAVA_BIN to the end of the path if it is not already there
|
||||
let java = if path.file_name()?.to_str()? != JAVA_BIN {
|
||||
let java = if path
|
||||
.file_name()
|
||||
.and_then(|x| x.to_str())
|
||||
.is_some_and(|x| x != JAVA_BIN)
|
||||
{
|
||||
path.join(JAVA_BIN)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
if !java.exists() {
|
||||
return None;
|
||||
return Err(JREError::NoExecutable(java).into());
|
||||
};
|
||||
|
||||
let bytes = include_bytes!("../../library/JavaInfo.class");
|
||||
let Ok(tempdir) = tempfile::tempdir() else {
|
||||
return None;
|
||||
};
|
||||
let file_path = tempdir.path().join("JavaInfo.class");
|
||||
io::write(&file_path, bytes).await.ok()?;
|
||||
let (_temp, file_path) =
|
||||
get_resource_file!("../../library" / "JavaInfo.class")?;
|
||||
|
||||
let output = Command::new(&java)
|
||||
.arg("-cp")
|
||||
.arg(file_path.parent().unwrap())
|
||||
.arg("JavaInfo")
|
||||
.env_remove("_JAVA_OPTIONS")
|
||||
.output()
|
||||
.ok()?;
|
||||
.output()?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
@@ -308,64 +302,49 @@ pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
|
||||
// Extract version info from it
|
||||
if let Some(arch) = java_arch {
|
||||
if let Some(version) = java_version {
|
||||
if let Ok((_, major_version)) =
|
||||
extract_java_majorminor_version(version)
|
||||
{
|
||||
if let Ok(version) = extract_java_version(version) {
|
||||
let path = java.to_string_lossy().to_string();
|
||||
return Some(JavaVersion {
|
||||
major_version,
|
||||
return Ok(JavaVersion {
|
||||
parsed_version: version,
|
||||
path,
|
||||
version: version.to_string(),
|
||||
architecture: arch.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
return Err(JREError::InvalidJREVersion(version.to_owned()).into());
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
Err(JREError::FailedJavaCheck(java).into())
|
||||
}
|
||||
|
||||
/// Extract major/minor version from a java version string
|
||||
/// Gets the minor version or an error, and assumes 1 for major version if it could not find
|
||||
/// "1.8.0_361" -> (1, 8)
|
||||
/// "20" -> (1, 20)
|
||||
pub fn extract_java_majorminor_version(
|
||||
version: &str,
|
||||
) -> Result<(u32, u32), JREError> {
|
||||
pub fn extract_java_version(version: &str) -> Result<u32, JREError> {
|
||||
let mut split = version.split('.');
|
||||
let major_opt = split.next();
|
||||
|
||||
let mut major;
|
||||
// Try minor. If doesn't exist, in format like "20" so use major
|
||||
let mut minor = if let Some(minor) = split.next() {
|
||||
major = major_opt.unwrap_or("1").parse::<u32>()?;
|
||||
minor.parse::<u32>()?
|
||||
} else {
|
||||
// Formatted like "20", only one value means that is minor version
|
||||
major = 1;
|
||||
major_opt
|
||||
.ok_or_else(|| JREError::InvalidJREVersion(version.to_string()))?
|
||||
.parse::<u32>()?
|
||||
};
|
||||
|
||||
// Java start should always be 1. If more than 1, it is formatted like "17.0.1.2" and starts with minor version
|
||||
if major > 1 {
|
||||
minor = major;
|
||||
major = 1;
|
||||
let version = split.next().unwrap();
|
||||
let version = version.split_once('-').map_or(version, |(x, _)| x);
|
||||
let mut version = version.parse::<u32>()?;
|
||||
if version == 1 {
|
||||
version = split.next().map_or(Ok(1), |x| x.parse::<u32>())?;
|
||||
}
|
||||
|
||||
Ok((major, minor))
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum JREError {
|
||||
#[error("Command error : {0}")]
|
||||
#[error("Command error: {0}")]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("Env error: {0}")]
|
||||
EnvError(#[from] env::VarError),
|
||||
|
||||
#[error("No JRE found for required version: {0}")]
|
||||
NoJREFound(String),
|
||||
#[error("No executable found at {0}")]
|
||||
NoExecutable(PathBuf),
|
||||
|
||||
#[error("Could not check Java version at path {0}")]
|
||||
FailedJavaCheck(PathBuf),
|
||||
|
||||
#[error("Invalid JRE version string: {0}")]
|
||||
InvalidJREVersion(String),
|
||||
@@ -376,9 +355,9 @@ pub enum JREError {
|
||||
#[error("Join error: {0}")]
|
||||
JoinError(#[from] JoinError),
|
||||
|
||||
#[error("No stored tag for Minecraft Version {0}")]
|
||||
#[error("No stored tag for Minecraft version {0}")]
|
||||
NoMinecraftVersionFound(String),
|
||||
|
||||
#[error("Error getting launcher sttae")]
|
||||
#[error("Error getting launcher state")]
|
||||
StateError,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user