You've already forked AstralRinth
forked from didirus/AstralRinth
Merge commit 'dbde3c4669af10dd577590ed6980e5bd4552d13c' into feature-clean
This commit is contained in:
@@ -166,9 +166,8 @@ pub async fn test_jre(
|
||||
path: PathBuf,
|
||||
major_version: u32,
|
||||
) -> crate::Result<bool> {
|
||||
let jre = match jre::check_java_at_filepath(&path).await {
|
||||
Some(jre) => jre,
|
||||
None => return Ok(false),
|
||||
let Some(jre) = jre::check_java_at_filepath(&path).await else {
|
||||
return Ok(false);
|
||||
};
|
||||
let (major, _) = extract_java_majorminor_version(&jre.version)?;
|
||||
Ok(major == major_version)
|
||||
|
||||
@@ -97,12 +97,15 @@ pub struct ATLauncherMod {
|
||||
|
||||
// Check if folder has a instance.json that parses
|
||||
pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool {
|
||||
let instance: String =
|
||||
io::read_to_string(&instance_folder.join("instance.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let instance: Result<ATInstance, serde_json::Error> =
|
||||
serde_json::from_str::<ATInstance>(&instance);
|
||||
let instance = serde_json::from_str::<ATInstance>(
|
||||
&io::read_any_encoding_to_string(
|
||||
&instance_folder.join("instance.json"),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(("".into(), encoding_rs::UTF_8))
|
||||
.0,
|
||||
);
|
||||
|
||||
if let Err(e) = instance {
|
||||
tracing::warn!(
|
||||
"Could not parse instance.json at {}: {}",
|
||||
@@ -124,14 +127,17 @@ pub async fn import_atlauncher(
|
||||
) -> crate::Result<()> {
|
||||
let atlauncher_instance_path = atlauncher_base_path
|
||||
.join("instances")
|
||||
.join(instance_folder.clone());
|
||||
.join(&instance_folder);
|
||||
|
||||
// Load instance.json
|
||||
let atinstance: String =
|
||||
io::read_to_string(&atlauncher_instance_path.join("instance.json"))
|
||||
.await?;
|
||||
let atinstance: ATInstance =
|
||||
serde_json::from_str::<ATInstance>(&atinstance)?;
|
||||
let atinstance = serde_json::from_str::<ATInstance>(
|
||||
&io::read_any_encoding_to_string(
|
||||
&atlauncher_instance_path.join("instance.json"),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(("".into(), encoding_rs::UTF_8))
|
||||
.0,
|
||||
)?;
|
||||
|
||||
// Icon path should be {instance_folder}/instance.png if it exists,
|
||||
// Second possibility is ATLauncher/configs/images/{safe_pack_name}.png (safe pack name is alphanumeric lowercase)
|
||||
|
||||
@@ -36,13 +36,15 @@ pub struct InstalledModpack {
|
||||
|
||||
// Check if folder has a minecraftinstance.json that parses
|
||||
pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool {
|
||||
let minecraftinstance: String =
|
||||
io::read_to_string(&instance_folder.join("minecraftinstance.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let minecraftinstance: Result<MinecraftInstance, serde_json::Error> =
|
||||
serde_json::from_str::<MinecraftInstance>(&minecraftinstance);
|
||||
minecraftinstance.is_ok()
|
||||
let minecraft_instance = serde_json::from_str::<MinecraftInstance>(
|
||||
&io::read_any_encoding_to_string(
|
||||
&instance_folder.join("minecraftinstance.json"),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(("".into(), encoding_rs::UTF_8))
|
||||
.0,
|
||||
);
|
||||
minecraft_instance.is_ok()
|
||||
}
|
||||
|
||||
pub async fn import_curseforge(
|
||||
@@ -50,19 +52,20 @@ pub async fn import_curseforge(
|
||||
profile_path: &str, // path to profile
|
||||
) -> crate::Result<()> {
|
||||
// Load minecraftinstance.json
|
||||
let minecraft_instance: String = io::read_to_string(
|
||||
&curseforge_instance_folder.join("minecraftinstance.json"),
|
||||
)
|
||||
.await?;
|
||||
let minecraft_instance: MinecraftInstance =
|
||||
serde_json::from_str::<MinecraftInstance>(&minecraft_instance)?;
|
||||
let override_title: Option<String> = minecraft_instance.name.clone();
|
||||
let minecraft_instance = serde_json::from_str::<MinecraftInstance>(
|
||||
&io::read_any_encoding_to_string(
|
||||
&curseforge_instance_folder.join("minecraftinstance.json"),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(("".into(), encoding_rs::UTF_8))
|
||||
.0,
|
||||
)?;
|
||||
let override_title = minecraft_instance.name;
|
||||
let backup_name = format!(
|
||||
"Curseforge-{}",
|
||||
curseforge_instance_folder
|
||||
.file_name()
|
||||
.map(|a| a.to_string_lossy().to_string())
|
||||
.unwrap_or("Unknown".to_string())
|
||||
.map_or("Unknown".to_string(), |a| a.to_string_lossy().to_string())
|
||||
);
|
||||
|
||||
let state = State::get().await?;
|
||||
|
||||
@@ -25,12 +25,12 @@ pub struct GDLauncherLoader {
|
||||
|
||||
// Check if folder has a config.json that parses
|
||||
pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool {
|
||||
let config: String =
|
||||
io::read_to_string(&instance_folder.join("config.json"))
|
||||
let config = serde_json::from_str::<GDLauncherConfig>(
|
||||
&io::read_any_encoding_to_string(&instance_folder.join("config.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let config: Result<GDLauncherConfig, serde_json::Error> =
|
||||
serde_json::from_str::<GDLauncherConfig>(&config);
|
||||
.unwrap_or(("".into(), encoding_rs::UTF_8))
|
||||
.0,
|
||||
);
|
||||
config.is_ok()
|
||||
}
|
||||
|
||||
@@ -39,18 +39,20 @@ pub async fn import_gdlauncher(
|
||||
profile_path: &str, // path to profile
|
||||
) -> crate::Result<()> {
|
||||
// Load config.json
|
||||
let config: String =
|
||||
io::read_to_string(&gdlauncher_instance_folder.join("config.json"))
|
||||
.await?;
|
||||
let config: GDLauncherConfig =
|
||||
serde_json::from_str::<GDLauncherConfig>(&config)?;
|
||||
let override_title: Option<String> = config.loader.source_name.clone();
|
||||
let config = serde_json::from_str::<GDLauncherConfig>(
|
||||
&io::read_any_encoding_to_string(
|
||||
&gdlauncher_instance_folder.join("config.json"),
|
||||
)
|
||||
.await
|
||||
.unwrap_or(("".into(), encoding_rs::UTF_8))
|
||||
.0,
|
||||
)?;
|
||||
let override_title = config.loader.source_name;
|
||||
let backup_name = format!(
|
||||
"GDLauncher-{}",
|
||||
gdlauncher_instance_folder
|
||||
.file_name()
|
||||
.map(|a| a.to_string_lossy().to_string())
|
||||
.unwrap_or("Unknown".to_string())
|
||||
.map_or("Unknown".to_string(), |a| a.to_string_lossy().to_string())
|
||||
);
|
||||
|
||||
// Re-cache icon
|
||||
|
||||
@@ -26,6 +26,7 @@ enum MMCInstanceEnum {
|
||||
struct MMCInstanceGeneral {
|
||||
pub general: MMCInstance,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct MMCInstance {
|
||||
@@ -144,9 +145,9 @@ pub async fn is_valid_mmc(instance_folder: PathBuf) -> bool {
|
||||
let instance_cfg = instance_folder.join("instance.cfg");
|
||||
let mmc_pack = instance_folder.join("mmc-pack.json");
|
||||
|
||||
let mmc_pack = match io::read_to_string(&mmc_pack).await {
|
||||
Ok(mmc_pack) => mmc_pack,
|
||||
Err(_) => return false,
|
||||
let Ok((mmc_pack, _)) = io::read_any_encoding_to_string(&mmc_pack).await
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
load_instance_cfg(&instance_cfg).await.is_ok()
|
||||
@@ -155,7 +156,7 @@ pub async fn is_valid_mmc(instance_folder: PathBuf) -> bool {
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_instances_subpath(config: PathBuf) -> Option<String> {
|
||||
let launcher = io::read_to_string(&config).await.ok()?;
|
||||
let launcher = io::read_any_encoding_to_string(&config).await.ok()?.0;
|
||||
let launcher: MMCLauncherEnum = serde_ini::from_str(&launcher).ok()?;
|
||||
match launcher {
|
||||
MMCLauncherEnum::General(p) => Some(p.general.instance_dir),
|
||||
@@ -165,10 +166,9 @@ pub async fn get_instances_subpath(config: PathBuf) -> Option<String> {
|
||||
|
||||
// Loading the INI (instance.cfg) file
|
||||
async fn load_instance_cfg(file_path: &Path) -> crate::Result<MMCInstance> {
|
||||
let instance_cfg: String = io::read_to_string(file_path).await?;
|
||||
let instance_cfg_enum: MMCInstanceEnum =
|
||||
serde_ini::from_str::<MMCInstanceEnum>(&instance_cfg)?;
|
||||
match instance_cfg_enum {
|
||||
match serde_ini::from_str::<MMCInstanceEnum>(
|
||||
&io::read_any_encoding_to_string(file_path).await?.0,
|
||||
)? {
|
||||
MMCInstanceEnum::General(instance_cfg) => Ok(instance_cfg.general),
|
||||
MMCInstanceEnum::Instance(instance_cfg) => Ok(instance_cfg),
|
||||
}
|
||||
@@ -183,9 +183,13 @@ pub async fn import_mmc(
|
||||
let mmc_instance_path =
|
||||
mmc_base_path.join("instances").join(instance_folder);
|
||||
|
||||
let mmc_pack =
|
||||
io::read_to_string(&mmc_instance_path.join("mmc-pack.json")).await?;
|
||||
let mmc_pack: MMCPack = serde_json::from_str::<MMCPack>(&mmc_pack)?;
|
||||
let mmc_pack = serde_json::from_str::<MMCPack>(
|
||||
&io::read_any_encoding_to_string(
|
||||
&mmc_instance_path.join("mmc-pack.json"),
|
||||
)
|
||||
.await?
|
||||
.0,
|
||||
)?;
|
||||
|
||||
let instance_cfg =
|
||||
load_instance_cfg(&mmc_instance_path.join("instance.cfg")).await?;
|
||||
@@ -230,7 +234,7 @@ pub async fn import_mmc(
|
||||
// Kept separate as we may in the future want to add special handling for modrinth managed packs
|
||||
import_mmc_unmanaged(profile_path, minecraft_folder, "Imported Modrinth Modpack".to_string(), description, mmc_pack).await?;
|
||||
}
|
||||
Some(MMCManagedPackType::Flame) | Some(MMCManagedPackType::ATLauncher) => {
|
||||
Some(MMCManagedPackType::Flame | MMCManagedPackType::ATLauncher) => {
|
||||
// For flame/atlauncher managed packs
|
||||
// Treat as unmanaged, but with 'minecraft' folder instead of '.minecraft'
|
||||
import_mmc_unmanaged(profile_path, minecraft_folder, "Imported Modpack".to_string(), description, mmc_pack).await?;
|
||||
@@ -243,7 +247,7 @@ pub async fn import_mmc(
|
||||
_ => return Err(crate::ErrorKind::InputError("Instance is managed, but managed pack type not specified in instance.cfg".to_string()).into())
|
||||
}
|
||||
} else {
|
||||
// Direclty import unmanaged pack
|
||||
// Directly import unmanaged pack
|
||||
import_mmc_unmanaged(
|
||||
profile_path,
|
||||
minecraft_folder,
|
||||
|
||||
@@ -357,9 +357,7 @@ pub async fn set_profile_information(
|
||||
}
|
||||
}
|
||||
|
||||
let game_version = if let Some(game_version) = game_version {
|
||||
game_version
|
||||
} else {
|
||||
let Some(game_version) = game_version else {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Pack did not specify Minecraft version".to_string(),
|
||||
)
|
||||
@@ -393,10 +391,7 @@ pub async fn set_profile_information(
|
||||
locked: if !ignore_lock {
|
||||
true
|
||||
} else {
|
||||
prof.linked_data
|
||||
.as_ref()
|
||||
.map(|x| x.locked)
|
||||
.unwrap_or(true)
|
||||
prof.linked_data.as_ref().is_none_or(|x| x.locked)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -152,8 +152,7 @@ pub async fn install_zipped_mrpack_files(
|
||||
if let Some(env) = project.env {
|
||||
if env
|
||||
.get(&EnvType::Client)
|
||||
.map(|x| x == &SideType::Unsupported)
|
||||
.unwrap_or(false)
|
||||
.is_some_and(|x| x == &SideType::Unsupported)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -586,7 +586,7 @@ pub async fn get_pack_export_candidates(
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &profile_base_dir))?
|
||||
{
|
||||
let path: PathBuf = entry.path();
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
// Two layers of files/folders if its a folder
|
||||
let mut read_dir = io::read_dir(&path).await?;
|
||||
@@ -595,10 +595,10 @@ pub async fn get_pack_export_candidates(
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &profile_base_dir))?
|
||||
{
|
||||
let path: PathBuf = entry.path();
|
||||
|
||||
path_list
|
||||
.push(pack_get_relative_path(&profile_base_dir, &path)?);
|
||||
path_list.push(pack_get_relative_path(
|
||||
&profile_base_dir,
|
||||
&entry.path(),
|
||||
)?);
|
||||
}
|
||||
} else {
|
||||
// One layer of files/folders if its a file
|
||||
@@ -644,7 +644,6 @@ pub async fn run(
|
||||
/// Run Minecraft using a profile, and credentials for authentication
|
||||
/// Returns Arc pointer to RwLock to Child
|
||||
#[tracing::instrument(skip(credentials))]
|
||||
|
||||
pub async fn run_credentials(
|
||||
path: &str,
|
||||
credentials: &Credentials,
|
||||
@@ -662,14 +661,15 @@ pub async fn run_credentials(
|
||||
.hooks
|
||||
.pre_launch
|
||||
.as_ref()
|
||||
.or(settings.hooks.pre_launch.as_ref());
|
||||
.or(settings.hooks.pre_launch.as_ref())
|
||||
.filter(|hook_command| !hook_command.is_empty());
|
||||
if let Some(hook) = pre_launch_hooks {
|
||||
// TODO: hook parameters
|
||||
let mut cmd = hook.split(' ');
|
||||
if let Some(command) = cmd.next() {
|
||||
let full_path = get_full_path(&profile.path).await?;
|
||||
let result = Command::new(command)
|
||||
.args(cmd.collect::<Vec<&str>>())
|
||||
.args(cmd)
|
||||
.current_dir(&full_path)
|
||||
.spawn()
|
||||
.map_err(|e| IOError::with_path(e, &full_path))?
|
||||
@@ -692,7 +692,12 @@ pub async fn run_credentials(
|
||||
.clone()
|
||||
.unwrap_or(settings.extra_launch_args);
|
||||
|
||||
let wrapper = profile.hooks.wrapper.clone().or(settings.hooks.wrapper);
|
||||
let wrapper = profile
|
||||
.hooks
|
||||
.wrapper
|
||||
.clone()
|
||||
.or(settings.hooks.wrapper)
|
||||
.filter(|hook_command| !hook_command.is_empty());
|
||||
|
||||
let memory = profile.memory.unwrap_or(settings.memory);
|
||||
let resolution =
|
||||
@@ -704,8 +709,12 @@ pub async fn run_credentials(
|
||||
.unwrap_or(settings.custom_env_vars);
|
||||
|
||||
// Post post exit hooks
|
||||
let post_exit_hook =
|
||||
profile.hooks.post_exit.clone().or(settings.hooks.post_exit);
|
||||
let post_exit_hook = profile
|
||||
.hooks
|
||||
.post_exit
|
||||
.clone()
|
||||
.or(settings.hooks.post_exit)
|
||||
.filter(|hook_command| !hook_command.is_empty());
|
||||
|
||||
// Any options.txt settings that we want set, add here
|
||||
let mut mc_set_options: Vec<(String, String)> = vec![];
|
||||
@@ -872,15 +881,12 @@ pub async fn create_mrpack_json(
|
||||
env.insert(EnvType::Client, SideType::Required);
|
||||
env.insert(EnvType::Server, SideType::Required);
|
||||
|
||||
let primary_file =
|
||||
if let Some(primary_file) = version.files.first() {
|
||||
primary_file
|
||||
} else {
|
||||
return Some(Err(crate::ErrorKind::OtherError(
|
||||
format!("No primary file found for mod at: {path}"),
|
||||
)
|
||||
.as_error()));
|
||||
};
|
||||
let Some(primary_file) = version.files.first() else {
|
||||
return Some(Err(crate::ErrorKind::OtherError(format!(
|
||||
"No primary file found for mod at: {path}"
|
||||
))
|
||||
.as_error()));
|
||||
};
|
||||
|
||||
let file_size = primary_file.size;
|
||||
let downloads = vec![primary_file.url.clone()];
|
||||
|
||||
@@ -255,7 +255,7 @@ async fn get_all_worlds_in_profile(
|
||||
AttachedWorldData::get_all_for_instance(profile_path, &state.pool)
|
||||
.await?;
|
||||
if !attached_data.is_empty() {
|
||||
for world in worlds.iter_mut() {
|
||||
for world in &mut worlds {
|
||||
if let Some(data) = attached_data
|
||||
.get(&(world.world_type(), world.world_id().to_owned()))
|
||||
{
|
||||
|
||||
@@ -139,9 +139,7 @@ pub async fn edit_loading(
|
||||
// increment refers to by what relative increment to the loading struct's total to update
|
||||
// message is the message to display on the loading bar- if None, use the loading bar's default one
|
||||
// By convention, fraction is the fraction of the progress bar that is filled
|
||||
#[allow(unused_variables)]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
|
||||
pub fn emit_loading(
|
||||
key: &LoadingBarId,
|
||||
increment_frac: f64,
|
||||
@@ -149,22 +147,13 @@ pub fn emit_loading(
|
||||
) -> crate::Result<()> {
|
||||
let event_state = crate::EventState::get()?;
|
||||
|
||||
let mut loading_bar = match event_state.loading_bars.get_mut(&key.0) {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
return Err(EventError::NoLoadingBar(key.0).into());
|
||||
}
|
||||
let Some(mut loading_bar) = event_state.loading_bars.get_mut(&key.0) else {
|
||||
return Err(EventError::NoLoadingBar(key.0).into());
|
||||
};
|
||||
|
||||
// Tick up loading bar
|
||||
loading_bar.current += increment_frac;
|
||||
let display_frac = loading_bar.current / loading_bar.total;
|
||||
let opt_display_frac = if display_frac >= 1.0 {
|
||||
None // by convention, when its done, we submit None
|
||||
// any further updates will be ignored (also sending None)
|
||||
} else {
|
||||
Some(display_frac)
|
||||
};
|
||||
|
||||
if f64::abs(display_frac - loading_bar.last_sent) > 0.005 {
|
||||
// Emit event to indicatif progress bar
|
||||
@@ -187,7 +176,12 @@ pub fn emit_loading(
|
||||
.emit(
|
||||
"loading",
|
||||
LoadingPayload {
|
||||
fraction: opt_display_frac,
|
||||
fraction: if display_frac >= 1.0 {
|
||||
None // by convention, when its done, we submit None
|
||||
// any further updates will be ignored (also sending None)
|
||||
} else {
|
||||
Some(display_frac)
|
||||
},
|
||||
message: message
|
||||
.unwrap_or(&loading_bar.message)
|
||||
.to_string(),
|
||||
@@ -197,6 +191,9 @@ pub fn emit_loading(
|
||||
)
|
||||
.map_err(EventError::from)?;
|
||||
|
||||
#[cfg(not(any(feature = "cli", feature = "tauri")))]
|
||||
let _ = message;
|
||||
|
||||
loading_bar.last_sent = display_frac;
|
||||
}
|
||||
|
||||
@@ -204,8 +201,6 @@ pub fn emit_loading(
|
||||
}
|
||||
|
||||
// emit_warning(message)
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
@@ -227,8 +222,6 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||
// emit_command(CommandPayload::Something { something })
|
||||
// ie: installing a pack, opening an .mrpack, etc
|
||||
// Generally used for url deep links and file opens that we want to handle in the frontend
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn emit_command(command: CommandPayload) -> crate::Result<()> {
|
||||
tracing::debug!("Command: {}", serde_json::to_string(&command)?);
|
||||
#[cfg(feature = "tauri")]
|
||||
|
||||
@@ -87,9 +87,9 @@ pub fn get_lib_path(
|
||||
lib: &str,
|
||||
allow_not_exist: bool,
|
||||
) -> crate::Result<String> {
|
||||
let mut path = libraries_path.to_path_buf();
|
||||
|
||||
path.push(get_path_from_artifact(lib)?);
|
||||
let path = libraries_path
|
||||
.to_path_buf()
|
||||
.join(get_path_from_artifact(lib)?);
|
||||
|
||||
if !path.exists() && allow_not_exist {
|
||||
return Ok(path.to_string_lossy().to_string());
|
||||
|
||||
@@ -37,12 +37,7 @@ pub async fn download_minecraft(
|
||||
let assets_index =
|
||||
download_assets_index(st, version, Some(loading_bar), force).await?;
|
||||
|
||||
let amount = if version
|
||||
.processors
|
||||
.as_ref()
|
||||
.map(|x| !x.is_empty())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let amount = if version.processors.as_ref().is_some_and(|x| !x.is_empty()) {
|
||||
25.0
|
||||
} else {
|
||||
40.0
|
||||
|
||||
@@ -15,9 +15,10 @@ use daedalus as d;
|
||||
use daedalus::minecraft::{LoggingSide, RuleAction, VersionInfo};
|
||||
use daedalus::modded::LoaderVersion;
|
||||
use rand::seq::SliceRandom; // AstralRinth
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use st::Profile;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use tokio::process::Command;
|
||||
|
||||
@@ -139,8 +140,7 @@ pub async fn get_java_version_from_profile(
|
||||
let key = version_info
|
||||
.java_version
|
||||
.as_ref()
|
||||
.map(|it| it.major_version)
|
||||
.unwrap_or(8);
|
||||
.map_or(8, |it| it.major_version);
|
||||
|
||||
let state = State::get().await?;
|
||||
|
||||
@@ -255,8 +255,7 @@ pub async fn install_minecraft(
|
||||
|
||||
let loader_version_id = loader_version.clone();
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.loader_version =
|
||||
loader_version_id.clone().map(|x| x.id.clone());
|
||||
prof.loader_version = loader_version_id.clone().map(|x| x.id);
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
@@ -281,8 +280,7 @@ pub async fn install_minecraft(
|
||||
let key = version_info
|
||||
.java_version
|
||||
.as_ref()
|
||||
.map(|it| it.major_version)
|
||||
.unwrap_or(8);
|
||||
.map_or(8, |it| it.major_version);
|
||||
let (java_version, set_java) = if let Some(java_version) =
|
||||
get_java_version_from_profile(profile, &version_info).await?
|
||||
{
|
||||
@@ -355,9 +353,11 @@ pub async fn install_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
let cp = wrap_ref_builder!(cp = processor.classpath.clone() => {
|
||||
cp.push(processor.jar.clone())
|
||||
});
|
||||
let cp = {
|
||||
let mut cp = processor.classpath.clone();
|
||||
cp.push(processor.jar.clone());
|
||||
cp
|
||||
};
|
||||
|
||||
let child = Command::new(&java_version.path)
|
||||
.arg("-cp")
|
||||
@@ -580,7 +580,9 @@ pub async fn launch_minecraft(
|
||||
let args = version_info.arguments.clone().unwrap_or_default();
|
||||
let mut command = match wrapper {
|
||||
Some(hook) => {
|
||||
wrap_ref_builder!(it = Command::new(hook) => {it.arg(&java_version.path)})
|
||||
let mut command = Command::new(hook);
|
||||
command.arg(&java_version.path);
|
||||
command
|
||||
}
|
||||
None => Command::new(&java_version.path),
|
||||
};
|
||||
@@ -629,8 +631,7 @@ pub async fn launch_minecraft(
|
||||
.as_ref()
|
||||
.and_then(|x| x.get(&LoggingSide::Client)),
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
.into_iter(),
|
||||
)
|
||||
.arg(version_info.main_class.clone())
|
||||
.args(
|
||||
@@ -648,8 +649,7 @@ pub async fn launch_minecraft(
|
||||
&java_version.architecture,
|
||||
quick_play_type,
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
.into_iter(),
|
||||
)
|
||||
.current_dir(instance_path.clone());
|
||||
|
||||
@@ -665,20 +665,35 @@ pub async fn launch_minecraft(
|
||||
|
||||
// Overwrites the minecraft options.txt file with the settings from the profile
|
||||
// Uses 'a:b' syntax which is not quite yaml
|
||||
use regex::Regex;
|
||||
|
||||
if !mc_set_options.is_empty() {
|
||||
let options_path = instance_path.join("options.txt");
|
||||
let mut options_string = String::new();
|
||||
if options_path.exists() {
|
||||
options_string = io::read_to_string(&options_path).await?;
|
||||
|
||||
let (mut options_string, input_encoding) = if options_path.exists() {
|
||||
io::read_any_encoding_to_string(&options_path).await?
|
||||
} else {
|
||||
(String::new(), encoding_rs::UTF_8)
|
||||
};
|
||||
|
||||
// UTF-16 encodings may be successfully detected and read, but we cannot encode
|
||||
// them back, and it's technically possible that the game client strongly expects
|
||||
// such encoding
|
||||
if input_encoding != input_encoding.output_encoding() {
|
||||
return Err(crate::ErrorKind::LauncherError(format!(
|
||||
"The instance options.txt file uses an unsupported encoding: {}. \
|
||||
Please either turn off instance options that need to modify this file, \
|
||||
or convert the file to an encoding that both the game and this app support, \
|
||||
such as UTF-8.",
|
||||
input_encoding.name()
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
for (key, value) in mc_set_options {
|
||||
let re = Regex::new(&format!(r"(?m)^{}:.*$", regex::escape(key)))?;
|
||||
// check if the regex exists in the file
|
||||
if !re.is_match(&options_string) {
|
||||
// The key was not found in the file, so append it
|
||||
options_string.push_str(&format!("\n{key}:{value}"));
|
||||
write!(&mut options_string, "\n{key}:{value}").unwrap();
|
||||
} else {
|
||||
let replaced_string = re
|
||||
.replace_all(&options_string, &format!("{key}:{value}"))
|
||||
@@ -687,7 +702,8 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
io::write(&options_path, options_string).await?;
|
||||
io::write(&options_path, input_encoding.encode(&options_string).0)
|
||||
.await?;
|
||||
}
|
||||
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
@@ -697,31 +713,6 @@ pub async fn launch_minecraft(
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut censor_strings = HashMap::new();
|
||||
let username = whoami::username();
|
||||
censor_strings
|
||||
.insert(format!("/{username}/"), "/{COMPUTER_USERNAME}/".to_string());
|
||||
censor_strings.insert(
|
||||
format!("\\{username}\\"),
|
||||
"\\{COMPUTER_USERNAME}\\".to_string(),
|
||||
);
|
||||
censor_strings.insert(
|
||||
credentials.access_token.clone(),
|
||||
"{MINECRAFT_ACCESS_TOKEN}".to_string(),
|
||||
);
|
||||
censor_strings.insert(
|
||||
credentials.username.clone(),
|
||||
"{MINECRAFT_USERNAME}".to_string(),
|
||||
);
|
||||
censor_strings.insert(
|
||||
credentials.id.as_simple().to_string(),
|
||||
"{MINECRAFT_UUID}".to_string(),
|
||||
);
|
||||
censor_strings.insert(
|
||||
credentials.id.as_hyphenated().to_string(),
|
||||
"{MINECRAFT_UUID}".to_string(),
|
||||
);
|
||||
|
||||
// If in tauri, and the 'minimize on launch' setting is enabled, minimize the window
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
|
||||
@@ -461,8 +461,7 @@ impl CacheValue {
|
||||
CacheValue::Team(members) => members
|
||||
.iter()
|
||||
.next()
|
||||
.map(|x| x.team_id.as_str())
|
||||
.unwrap_or(DEFAULT_ID)
|
||||
.map_or(DEFAULT_ID, |x| x.team_id.as_str())
|
||||
.to_string(),
|
||||
CacheValue::Organization(org) => org.id.clone(),
|
||||
CacheValue::File(file) => file.hash.clone(),
|
||||
@@ -556,7 +555,6 @@ macro_rules! impl_cache_methods {
|
||||
$(
|
||||
paste::paste! {
|
||||
#[tracing::instrument(skip(pool, fetch_semaphore))]
|
||||
#[allow(dead_code)]
|
||||
pub async fn [<get_ $variant:snake>](
|
||||
id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -568,7 +566,6 @@ macro_rules! impl_cache_methods {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(pool, fetch_semaphore))]
|
||||
#[allow(dead_code)]
|
||||
pub async fn [<get_ $variant:snake _many>](
|
||||
ids: &[&str],
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -597,7 +594,6 @@ macro_rules! impl_cache_method_singular {
|
||||
$(
|
||||
paste::paste! {
|
||||
#[tracing::instrument(skip(pool, fetch_semaphore))]
|
||||
#[allow(dead_code)]
|
||||
pub async fn [<get_ $variant:snake>] (
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
pool: &SqlitePool,
|
||||
@@ -735,18 +731,13 @@ impl CachedEntry {
|
||||
|
||||
remaining_keys.retain(|x| {
|
||||
x != &&*row.id
|
||||
&& !row
|
||||
.alias
|
||||
.as_ref()
|
||||
.map(|y| {
|
||||
if type_.case_sensitive_alias().unwrap_or(true)
|
||||
{
|
||||
x == y
|
||||
} else {
|
||||
y.to_lowercase() == x.to_lowercase()
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
&& !row.alias.as_ref().is_some_and(|y| {
|
||||
if type_.case_sensitive_alias().unwrap_or(true) {
|
||||
x == y
|
||||
} else {
|
||||
y.to_lowercase() == x.to_lowercase()
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(data) = parsed_data {
|
||||
@@ -991,7 +982,7 @@ impl CachedEntry {
|
||||
let key = key.to_string();
|
||||
|
||||
if let Some(position) = teams.iter().position(|x| {
|
||||
x.first().map(|x| x.team_id == key).unwrap_or(false)
|
||||
x.first().is_some_and(|x| x.team_id == key)
|
||||
}) {
|
||||
let team = teams.remove(position);
|
||||
|
||||
|
||||
@@ -47,9 +47,8 @@ impl DirectoryInfo {
|
||||
))
|
||||
})?;
|
||||
|
||||
let config_dir = config_dir
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| settings_dir.clone());
|
||||
let config_dir =
|
||||
config_dir.map_or_else(|| settings_dir.clone(), PathBuf::from);
|
||||
|
||||
Ok(Self {
|
||||
settings_dir,
|
||||
@@ -198,8 +197,7 @@ impl DirectoryInfo {
|
||||
let move_dir = settings
|
||||
.custom_dir
|
||||
.as_ref()
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| app_dir.clone());
|
||||
.map_or_else(|| app_dir.clone(), PathBuf::from);
|
||||
|
||||
async fn is_dir_writeable(
|
||||
new_config_dir: &Path,
|
||||
@@ -225,7 +223,7 @@ impl DirectoryInfo {
|
||||
|
||||
let disks = sysinfo::Disks::new_with_refreshed_list();
|
||||
|
||||
for disk in disks.iter() {
|
||||
for disk in &disks {
|
||||
if path.starts_with(disk.mount_point()) {
|
||||
return Ok(Some(disk.available_space()));
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ impl FriendsSocket {
|
||||
ServerToClientMessage::FriendRequest { from } => {
|
||||
let _ = emit_friend(FriendPayload::FriendRequest { from }).await;
|
||||
}
|
||||
ServerToClientMessage::FriendRequestRejected { .. } => todo!(),
|
||||
ServerToClientMessage::FriendRequestRejected { .. } => {}, // TODO
|
||||
|
||||
ServerToClientMessage::FriendSocketListening { .. } => {}, // TODO
|
||||
ServerToClientMessage::FriendSocketStoppedListening { .. } => {}, // TODO
|
||||
|
||||
@@ -29,9 +29,7 @@ where
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let old_launcher_root = if let Some(dir) = default_settings_dir() {
|
||||
dir
|
||||
} else {
|
||||
let Some(old_launcher_root) = default_settings_dir() else {
|
||||
return Ok(());
|
||||
};
|
||||
let old_launcher_root_str = old_launcher_root.to_string_lossy().to_string();
|
||||
@@ -177,12 +175,10 @@ where
|
||||
|
||||
let profile_path = entry.path().join("profile.json");
|
||||
|
||||
let profile = if let Ok(profile) =
|
||||
let Ok(profile) =
|
||||
read_json::<LegacyProfile>(&profile_path, &io_semaphore)
|
||||
.await
|
||||
{
|
||||
profile
|
||||
} else {
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -285,7 +281,7 @@ where
|
||||
|
||||
TeamMember {
|
||||
team_id: x.team_id,
|
||||
user: user.clone(),
|
||||
user,
|
||||
is_owner: x.role == "Owner",
|
||||
role: x.role,
|
||||
ordering: x.ordering,
|
||||
|
||||
@@ -1177,12 +1177,10 @@ fn get_date_header(headers: &HeaderMap) -> DateTime<Utc> {
|
||||
.get(reqwest::header::DATE)
|
||||
.and_then(|x| x.to_str().ok())
|
||||
.and_then(|x| DateTime::parse_from_rfc2822(x).ok())
|
||||
.map(|x| x.with_timezone(&Utc))
|
||||
.unwrap_or(Utc::now())
|
||||
.map_or(Utc::now(), |x| x.with_timezone(&Utc))
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[allow(clippy::format_collect)]
|
||||
fn generate_oauth_challenge() -> String {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
|
||||
@@ -692,7 +692,7 @@ impl Process {
|
||||
let mut cmd = hook.split(' ');
|
||||
if let Some(command) = cmd.next() {
|
||||
let mut command = Command::new(command);
|
||||
command.args(cmd.collect::<Vec<&str>>()).current_dir(
|
||||
command.args(cmd).current_dir(
|
||||
profile::get_full_path(&profile_path).await?,
|
||||
);
|
||||
command.spawn().map_err(IOError::from)?;
|
||||
|
||||
@@ -1022,8 +1022,10 @@ impl Profile {
|
||||
file.hash,
|
||||
file.project_type
|
||||
.filter(|x| *x != ProjectType::Mod)
|
||||
.map(|x| x.get_loaders().join("+"))
|
||||
.unwrap_or_else(|| profile.loader.as_str().to_string()),
|
||||
.map_or_else(
|
||||
|| profile.loader.as_str().to_string(),
|
||||
|x| x.get_loaders().join("+")
|
||||
),
|
||||
profile.game_version
|
||||
)
|
||||
}
|
||||
|
||||
@@ -247,9 +247,13 @@ pub struct WindowSize(pub u16, pub u16);
|
||||
|
||||
/// Game initialization hooks
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde_with::serde_as]
|
||||
pub struct Hooks {
|
||||
#[serde_as(as = "serde_with::NoneAsEmptyString")]
|
||||
pub pre_launch: Option<String>,
|
||||
#[serde_as(as = "serde_with::NoneAsEmptyString")]
|
||||
pub wrapper: Option<String>,
|
||||
#[serde_as(as = "serde_with::NoneAsEmptyString")]
|
||||
pub post_exit: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -80,10 +80,9 @@ pub async fn fetch_advanced(
|
||||
) -> crate::Result<Bytes> {
|
||||
let _permit = semaphore.0.acquire().await?;
|
||||
|
||||
let creds = if !header
|
||||
let creds = if header
|
||||
.as_ref()
|
||||
.map(|x| &*x.0.to_lowercase() == "authorization")
|
||||
.unwrap_or(false)
|
||||
.is_none_or(|x| &*x.0.to_lowercase() != "authorization")
|
||||
&& (url.starts_with("https://cdn.modrinth.com")
|
||||
|| url.starts_with(MODRINTH_API_URL)
|
||||
|| url.starts_with(MODRINTH_API_URL_V3))
|
||||
|
||||
@@ -35,7 +35,6 @@ impl IOError {
|
||||
}
|
||||
}
|
||||
|
||||
// dunce canonicalize
|
||||
pub fn canonicalize(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<std::path::PathBuf, IOError> {
|
||||
@@ -46,7 +45,6 @@ pub fn canonicalize(
|
||||
})
|
||||
}
|
||||
|
||||
// read_dir
|
||||
pub async fn read_dir(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<tokio::fs::ReadDir, IOError> {
|
||||
@@ -59,7 +57,6 @@ pub async fn read_dir(
|
||||
})
|
||||
}
|
||||
|
||||
// create_dir
|
||||
pub async fn create_dir(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<(), IOError> {
|
||||
@@ -72,7 +69,6 @@ pub async fn create_dir(
|
||||
})
|
||||
}
|
||||
|
||||
// create_dir_all
|
||||
pub async fn create_dir_all(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<(), IOError> {
|
||||
@@ -85,7 +81,6 @@ pub async fn create_dir_all(
|
||||
})
|
||||
}
|
||||
|
||||
// remove_dir_all
|
||||
pub async fn remove_dir_all(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<(), IOError> {
|
||||
@@ -98,20 +93,37 @@ pub async fn remove_dir_all(
|
||||
})
|
||||
}
|
||||
|
||||
// read_to_string
|
||||
pub async fn read_to_string(
|
||||
/// Reads a text file to a string, automatically detecting its encoding and
|
||||
/// substituting any invalid characters with the Unicode replacement character.
|
||||
///
|
||||
/// This function is best suited for reading Minecraft instance files, whose
|
||||
/// encoding may vary depending on the platform, launchers, client versions
|
||||
/// (older Minecraft versions tended to rely on the system's default codepage
|
||||
/// more on Windows platforms), and mods used, while not being highly sensitive
|
||||
/// to occasional occurrences of mojibake or character replacements.
|
||||
pub async fn read_any_encoding_to_string(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<String, IOError> {
|
||||
) -> Result<(String, &'static encoding_rs::Encoding), IOError> {
|
||||
let path = path.as_ref();
|
||||
tokio::fs::read_to_string(path)
|
||||
.await
|
||||
.map_err(|e| IOError::IOPathError {
|
||||
source: e,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
})
|
||||
let file_bytes =
|
||||
tokio::fs::read(path)
|
||||
.await
|
||||
.map_err(|e| IOError::IOPathError {
|
||||
source: e,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
})?;
|
||||
|
||||
let file_encoding = {
|
||||
let mut encoding_detector = chardetng::EncodingDetector::new();
|
||||
encoding_detector.feed(&file_bytes, true);
|
||||
encoding_detector.guess(None, true)
|
||||
};
|
||||
|
||||
let (file_string, actual_file_encoding, _) =
|
||||
file_encoding.decode(&file_bytes);
|
||||
Ok((file_string.to_string(), actual_file_encoding))
|
||||
}
|
||||
|
||||
// read
|
||||
pub async fn read(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<Vec<u8>, IOError> {
|
||||
@@ -124,7 +136,6 @@ pub async fn read(
|
||||
})
|
||||
}
|
||||
|
||||
// write
|
||||
pub async fn write(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
data: impl AsRef<[u8]>,
|
||||
@@ -186,7 +197,6 @@ pub fn is_same_disk(old_dir: &Path, new_dir: &Path) -> Result<bool, IOError> {
|
||||
}
|
||||
}
|
||||
|
||||
// rename
|
||||
pub async fn rename_or_move(
|
||||
from: impl AsRef<std::path::Path>,
|
||||
to: impl AsRef<std::path::Path>,
|
||||
@@ -228,7 +238,6 @@ async fn move_recursive(from: &Path, to: &Path) -> Result<(), IOError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// copy
|
||||
pub async fn copy(
|
||||
from: impl AsRef<std::path::Path>,
|
||||
to: impl AsRef<std::path::Path>,
|
||||
@@ -243,7 +252,6 @@ pub async fn copy(
|
||||
})
|
||||
}
|
||||
|
||||
// remove file
|
||||
pub async fn remove_file(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<(), IOError> {
|
||||
@@ -256,7 +264,6 @@ pub async fn remove_file(
|
||||
})
|
||||
}
|
||||
|
||||
// open file
|
||||
pub async fn open_file(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<tokio::fs::File, IOError> {
|
||||
@@ -269,7 +276,6 @@ pub async fn open_file(
|
||||
})
|
||||
}
|
||||
|
||||
// remove dir
|
||||
pub async fn remove_dir(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<(), IOError> {
|
||||
@@ -282,7 +288,6 @@ pub async fn remove_dir(
|
||||
})
|
||||
}
|
||||
|
||||
// metadata
|
||||
pub async fn metadata(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<std::fs::Metadata, IOError> {
|
||||
|
||||
@@ -227,13 +227,11 @@ async fn get_all_jre_path() -> HashSet<PathBuf> {
|
||||
paths.unwrap_or_else(|_| HashSet::new())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[allow(dead_code)]
|
||||
pub const JAVA_BIN: &str = "javaw.exe";
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[allow(dead_code)]
|
||||
pub const JAVA_BIN: &str = "java";
|
||||
pub const JAVA_BIN: &str = if cfg!(target_os = "windows") {
|
||||
"javaw.exe"
|
||||
} else {
|
||||
"java"
|
||||
};
|
||||
|
||||
// For each example filepath in 'paths', perform check_java_at_filepath, checking each one concurrently
|
||||
// and returning a JavaVersion for every valid path that points to a java bin
|
||||
@@ -249,7 +247,7 @@ pub async fn check_java_at_filepaths(
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
jres.into_iter().flat_map(|x| x.ok()).flatten().collect()
|
||||
jres.into_iter().filter_map(|x| x.ok()).flatten().collect()
|
||||
}
|
||||
|
||||
// For example filepath 'path', attempt to resolve it and get a Java version at this path
|
||||
|
||||
@@ -5,15 +5,3 @@ pub mod jre;
|
||||
pub mod platform;
|
||||
pub mod utils; // AstralRinth
|
||||
pub mod server_ping;
|
||||
|
||||
/// Wrap a builder which uses a mut reference into one which outputs an owned value
|
||||
macro_rules! wrap_ref_builder {
|
||||
($id:ident = $init:expr => $transform:block) => {{
|
||||
let mut it = $init;
|
||||
{
|
||||
let $id = &mut it;
|
||||
$transform;
|
||||
}
|
||||
it
|
||||
}};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user