Profile imports (#348)
* temporary switch * draft; working unmanaged + modrinth * working update * added checkerg * fixed io merge issue * Added api handling * tidying up * reverted playground changes * fixed js issue * fixed merge issues
This commit is contained in:
parent
3fa33dc241
commit
70aaf6eef9
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -56,4 +56,5 @@
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"./theseus/Cargo.toml"
|
||||
],
|
||||
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||
}
|
||||
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -3528,6 +3528,12 @@ dependencies = [
|
||||
"winreg 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "result"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560"
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.10.0"
|
||||
@ -3827,6 +3833,17 @@ dependencies = [
|
||||
"syn 2.0.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_ini"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb236687e2bb073a7521c021949be944641e671b8505a94069ca37b656c81139"
|
||||
dependencies = [
|
||||
"result",
|
||||
"serde",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.99"
|
||||
@ -4599,6 +4616,7 @@ dependencies = [
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_ini",
|
||||
"serde_json",
|
||||
"sha1 0.6.1",
|
||||
"sha2 0.9.9",
|
||||
@ -5237,6 +5255,12 @@ version = "0.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "vswhom"
|
||||
version = "0.1.0"
|
||||
|
||||
@ -12,6 +12,7 @@ theseus_macros = { path = "../theseus_macros" }
|
||||
bytes = "1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_ini = "0.2.0"
|
||||
toml = "0.7.3"
|
||||
sha1 = { version = "0.6.1", features = ["std"]}
|
||||
sha2 = "0.9.9"
|
||||
|
||||
244
theseus/src/api/pack/import/atlauncher.rs
Normal file
244
theseus/src/api/pack/import/atlauncher.rs
Normal file
@ -0,0 +1,244 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
event::LoadingBarId,
|
||||
pack::{
|
||||
self,
|
||||
import::{self, copy_dotminecraft},
|
||||
install_from::CreatePackDescription,
|
||||
},
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
state::{LinkedData, ProfileInstallStage},
|
||||
util::io,
|
||||
State,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ATInstance {
|
||||
pub id: String, // minecraft version id ie: 1.12.1, not a name
|
||||
pub launcher: ATLauncher,
|
||||
pub java_version: ATJavaVersion,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ATLauncher {
|
||||
pub name: String,
|
||||
pub pack: String,
|
||||
pub version: String, // ie: 1.6
|
||||
pub loader_version: ATLauncherLoaderVersion,
|
||||
|
||||
pub modrinth_project: Option<ATLauncherModrinthProject>,
|
||||
pub modrinth_version: Option<ATLauncherModrinthVersion>,
|
||||
pub modrinth_manifest: Option<pack::install_from::PackFormat>,
|
||||
|
||||
pub mods: Vec<ATLauncherMod>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ATJavaVersion {
|
||||
pub major_version: u8,
|
||||
pub component: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ATLauncherLoaderVersion {
|
||||
pub r#type: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ATLauncherModrinthProject {
|
||||
pub id: String,
|
||||
pub slug: String,
|
||||
pub project_type: String,
|
||||
pub team: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub body: String,
|
||||
pub client_side: Option<String>,
|
||||
pub server_side: Option<String>,
|
||||
pub categories: Vec<String>,
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ATLauncherModrinthVersion {
|
||||
pub id: String,
|
||||
pub project_id: String,
|
||||
pub name: String,
|
||||
pub version_number: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ATLauncherModrinthVersionFile {
|
||||
pub hashes: HashMap<String, String>,
|
||||
pub url: String,
|
||||
pub filename: String,
|
||||
pub primary: bool,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ATLauncherModrinthVersionDependency {
|
||||
pub project_id: Option<String>,
|
||||
pub version_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ATLauncherMod {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub file: String,
|
||||
|
||||
pub modrinth_project: Option<ATLauncherModrinthProject>,
|
||||
pub modrinth_version: Option<ATLauncherModrinthVersion>,
|
||||
}
|
||||
|
||||
// Check if folder has a instance.json that parses
|
||||
pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool {
|
||||
let instance: String =
|
||||
fs::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);
|
||||
instance.is_ok()
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn import_atlauncher(
|
||||
atlauncher_base_path: PathBuf, // path to base atlauncher folder
|
||||
instance_folder: String, // instance folder in atlauncher_base_path
|
||||
profile_path: ProfilePathId, // path to profile
|
||||
) -> crate::Result<()> {
|
||||
let atlauncher_instance_path = atlauncher_base_path
|
||||
.join("instances")
|
||||
.join(instance_folder.clone());
|
||||
|
||||
// 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)?;
|
||||
|
||||
// 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)
|
||||
let icon_path_primary = atlauncher_instance_path.join("instance.png");
|
||||
let safe_pack_name = atinstance
|
||||
.launcher
|
||||
.pack
|
||||
.replace(|c: char| !c.is_alphanumeric(), "")
|
||||
.to_lowercase();
|
||||
let icon_path_secondary = atlauncher_base_path
|
||||
.join("configs")
|
||||
.join("images")
|
||||
.join(safe_pack_name + ".png");
|
||||
let icon = match (icon_path_primary.exists(), icon_path_secondary.exists())
|
||||
{
|
||||
(true, _) => import::recache_icon(icon_path_primary).await?,
|
||||
(_, true) => import::recache_icon(icon_path_secondary).await?,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Create description from instance.cfg
|
||||
let description = CreatePackDescription {
|
||||
icon,
|
||||
override_title: Some(atinstance.launcher.name.clone()),
|
||||
project_id: None,
|
||||
version_id: None,
|
||||
existing_loading_bar: None,
|
||||
profile_path: profile_path.clone(),
|
||||
};
|
||||
|
||||
let backup_name = format!("ATLauncher-{}", instance_folder);
|
||||
let minecraft_folder = atlauncher_instance_path;
|
||||
|
||||
import_atlauncher_unmanaged(
|
||||
profile_path,
|
||||
minecraft_folder,
|
||||
backup_name,
|
||||
description,
|
||||
atinstance,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn import_atlauncher_unmanaged(
|
||||
profile_path: ProfilePathId,
|
||||
minecraft_folder: PathBuf,
|
||||
backup_name: String,
|
||||
description: CreatePackDescription,
|
||||
atinstance: ATInstance,
|
||||
existing_loading_bar: Option<LoadingBarId>,
|
||||
) -> crate::Result<()> {
|
||||
let mod_loader = format!(
|
||||
"\"{}\"",
|
||||
atinstance.launcher.loader_version.r#type.to_lowercase()
|
||||
);
|
||||
let mod_loader: ModLoader = serde_json::from_str::<ModLoader>(&mod_loader)
|
||||
.map_err(|_| {
|
||||
crate::ErrorKind::InputError(format!(
|
||||
"Could not parse mod loader type: {}",
|
||||
mod_loader
|
||||
))
|
||||
})?;
|
||||
|
||||
let game_version = atinstance.id;
|
||||
|
||||
let loader_version = if mod_loader != ModLoader::Vanilla {
|
||||
crate::profile_create::get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
mod_loader,
|
||||
Some(atinstance.launcher.loader_version.version.clone()),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Set profile data to created default profile
|
||||
crate::api::profile::edit(&profile_path, |prof| {
|
||||
prof.metadata.name = description
|
||||
.override_title
|
||||
.clone()
|
||||
.unwrap_or_else(|| backup_name.to_string());
|
||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||
prof.metadata.linked_data = Some(LinkedData {
|
||||
project_id: description.project_id.clone(),
|
||||
version_id: description.version_id.clone(),
|
||||
});
|
||||
prof.metadata.icon = description.icon.clone();
|
||||
prof.metadata.game_version = game_version.clone();
|
||||
prof.metadata.loader_version = loader_version.clone();
|
||||
prof.metadata.loader = mod_loader;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc)
|
||||
copy_dotminecraft(profile_path.clone(), minecraft_folder).await?;
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(&profile_val, existing_loading_bar)
|
||||
.await?;
|
||||
|
||||
State::sync().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
161
theseus/src/api/pack/import/curseforge.rs
Normal file
161
theseus/src/api/pack/import/curseforge.rs
Normal file
@ -0,0 +1,161 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
state::ProfileInstallStage,
|
||||
util::io,
|
||||
State,
|
||||
};
|
||||
|
||||
use super::copy_dotminecraft;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FlameManifest {
|
||||
pub manifest_version: u8,
|
||||
pub name: String,
|
||||
pub minecraft: FlameMinecraft,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FlameMinecraft {
|
||||
pub version: String,
|
||||
pub mod_loaders: Vec<FlameModLoader>,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FlameModLoader {
|
||||
pub id: String,
|
||||
pub primary: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MinecraftInstance {
|
||||
pub name: Option<String>,
|
||||
pub game_version: String, // Minecraft game version. Non-prioritized, use this if Vanilla
|
||||
}
|
||||
|
||||
// Check if folder has a minecraftinstance.json that parses
|
||||
pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool {
|
||||
let minecraftinstance: String =
|
||||
fs::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()
|
||||
}
|
||||
|
||||
pub async fn import_curseforge(
|
||||
curseforge_instance_folder: PathBuf, // instance's folder
|
||||
profile_path: ProfilePathId, // path to profile
|
||||
) -> crate::Result<()> {
|
||||
// TODO: recache curseforge instance icon
|
||||
let icon: Option<PathBuf> = None;
|
||||
|
||||
// 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 backup_name = format!(
|
||||
"Curseforge-{}",
|
||||
curseforge_instance_folder
|
||||
.file_name()
|
||||
.map(|a| a.to_string_lossy().to_string())
|
||||
.unwrap_or("Unknown".to_string())
|
||||
);
|
||||
|
||||
// Curseforge vanilla profile may not have a manifest.json, so we allow it to not exist
|
||||
if curseforge_instance_folder.join("manifest.json").exists() {
|
||||
// Load manifest.json
|
||||
let cf_manifest: String = io::read_to_string(
|
||||
&curseforge_instance_folder.join("manifest.json"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let cf_manifest: FlameManifest =
|
||||
serde_json::from_str::<FlameManifest>(&cf_manifest)?;
|
||||
|
||||
let game_version = cf_manifest.minecraft.version;
|
||||
|
||||
// CF allows Forge, Fabric, and Vanilla
|
||||
let mut mod_loader = None;
|
||||
let mut loader_version = None;
|
||||
for loader in cf_manifest.minecraft.mod_loaders {
|
||||
match loader.id.split_once('-') {
|
||||
Some(("forge", version)) => {
|
||||
mod_loader = Some(ModLoader::Forge);
|
||||
loader_version = Some(version.to_string());
|
||||
}
|
||||
Some(("fabric", version)) => {
|
||||
mod_loader = Some(ModLoader::Fabric);
|
||||
loader_version = Some(version.to_string());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla);
|
||||
|
||||
let loader_version = if mod_loader != ModLoader::Vanilla {
|
||||
crate::profile_create::get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
mod_loader,
|
||||
loader_version,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Set profile data to created default profile
|
||||
crate::api::profile::edit(&profile_path, |prof| {
|
||||
prof.metadata.name = override_title
|
||||
.clone()
|
||||
.unwrap_or_else(|| backup_name.to_string());
|
||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||
prof.metadata.icon = icon.clone();
|
||||
prof.metadata.game_version = game_version.clone();
|
||||
prof.metadata.loader_version = loader_version.clone();
|
||||
prof.metadata.loader = mod_loader;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
} else {
|
||||
// If no manifest is found, it's a vanilla profile
|
||||
crate::api::profile::edit(&profile_path, |prof| {
|
||||
prof.metadata.name = override_title
|
||||
.clone()
|
||||
.unwrap_or_else(|| backup_name.to_string());
|
||||
prof.metadata.icon = icon.clone();
|
||||
prof.metadata.game_version =
|
||||
minecraft_instance.game_version.clone();
|
||||
prof.metadata.loader_version = None;
|
||||
prof.metadata.loader = ModLoader::Vanilla;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Copy in contained folders as overrides
|
||||
copy_dotminecraft(profile_path.clone(), curseforge_instance_folder).await?;
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(&profile_val, None).await?;
|
||||
|
||||
State::sync().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
115
theseus/src/api/pack/import/gdlauncher.rs
Normal file
115
theseus/src/api/pack/import/gdlauncher.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
state::ProfileInstallStage,
|
||||
util::io,
|
||||
State,
|
||||
};
|
||||
|
||||
use super::{copy_dotminecraft, recache_icon};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GDLauncherConfig {
|
||||
pub background: Option<String>,
|
||||
pub loader: GDLauncherLoader,
|
||||
// pub mods: Vec<GDLauncherMod>,
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GDLauncherLoader {
|
||||
pub loader_type: ModLoader,
|
||||
pub loader_version: Option<String>,
|
||||
pub mc_version: String,
|
||||
pub source: Option<String>,
|
||||
pub source_name: Option<String>,
|
||||
}
|
||||
|
||||
// Check if folder has a config.json that parses
|
||||
pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool {
|
||||
let config: String =
|
||||
fs::read_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);
|
||||
config.is_ok()
|
||||
}
|
||||
|
||||
pub async fn import_gdlauncher(
|
||||
gdlauncher_instance_folder: PathBuf, // instance's folder
|
||||
profile_path: ProfilePathId, // 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 backup_name = format!(
|
||||
"GDLauncher-{}",
|
||||
gdlauncher_instance_folder
|
||||
.file_name()
|
||||
.map(|a| a.to_string_lossy().to_string())
|
||||
.unwrap_or("Unknown".to_string())
|
||||
);
|
||||
|
||||
// Re-cache icon
|
||||
let icon = config
|
||||
.background
|
||||
.clone()
|
||||
.map(|b| gdlauncher_instance_folder.join(b));
|
||||
let icon = if let Some(icon) = icon {
|
||||
recache_icon(icon).await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let game_version = config.loader.mc_version;
|
||||
let mod_loader = config.loader.loader_type;
|
||||
let loader_version = config.loader.loader_version;
|
||||
|
||||
let loader_version = if mod_loader != ModLoader::Vanilla {
|
||||
crate::profile_create::get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
mod_loader,
|
||||
loader_version,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Set profile data to created default profile
|
||||
crate::api::profile::edit(&profile_path, |prof| {
|
||||
prof.metadata.name = override_title
|
||||
.clone()
|
||||
.unwrap_or_else(|| backup_name.to_string());
|
||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||
prof.metadata.icon = icon.clone();
|
||||
prof.metadata.game_version = game_version.clone();
|
||||
prof.metadata.loader_version = loader_version.clone();
|
||||
prof.metadata.loader = mod_loader;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Copy in contained folders as overrides
|
||||
copy_dotminecraft(profile_path.clone(), gdlauncher_instance_folder).await?;
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(&profile_val, None).await?;
|
||||
|
||||
State::sync().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
292
theseus/src/api/pack/import/mmc.rs
Normal file
292
theseus/src/api/pack/import/mmc.rs
Normal file
@ -0,0 +1,292 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
pack::{
|
||||
import::{self, copy_dotminecraft},
|
||||
install_from::{self, CreatePackDescription, PackDependency},
|
||||
},
|
||||
prelude::ProfilePathId,
|
||||
util::io,
|
||||
State,
|
||||
};
|
||||
|
||||
// instance.cfg
|
||||
// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/MinecraftInstance.cpp
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
#[serde(untagged)]
|
||||
enum MMCInstanceEnum {
|
||||
General(MMCInstanceGeneral),
|
||||
Instance(MMCInstance),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct MMCInstanceGeneral {
|
||||
pub general: MMCInstance,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct MMCInstance {
|
||||
pub java_path: Option<String>,
|
||||
pub jvm_args: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_bool")]
|
||||
pub managed_pack: Option<bool>,
|
||||
|
||||
pub managed_pack_id: Option<String>,
|
||||
pub managed_pack_type: Option<MMCManagedPackType>,
|
||||
pub managed_pack_version_id: Option<String>,
|
||||
pub managed_pack_version_name: Option<String>,
|
||||
|
||||
#[serde(rename = "iconKey")]
|
||||
pub icon_key: Option<String>,
|
||||
#[serde(rename = "name")]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
// serde_ini reads 'true' and 'false' as strings, so we need to convert them to booleans
|
||||
fn deserialize_optional_bool<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<bool>, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let s = Option::<String>::deserialize(deserializer)?;
|
||||
match s {
|
||||
Some(string) => match string.as_str() {
|
||||
"true" => Ok(Some(true)),
|
||||
"false" => Ok(Some(false)),
|
||||
_ => Err(de::Error::custom("expected 'true' or 'false'")),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MMCManagedPackType {
|
||||
Modrinth,
|
||||
Flame,
|
||||
ATLauncher,
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// mmc-pack.json
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MMCPack {
|
||||
components: Vec<MMCComponent>,
|
||||
format_version: u32,
|
||||
}
|
||||
|
||||
// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/Component.h
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MMCComponent {
|
||||
pub uid: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub version: Option<String>,
|
||||
#[serde(default)]
|
||||
pub dependency_only: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub important: bool,
|
||||
#[serde(default)]
|
||||
pub disabled: bool,
|
||||
|
||||
pub cached_name: Option<String>,
|
||||
pub cached_version: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub cached_requires: Vec<MMCComponentRequirement>,
|
||||
#[serde(default)]
|
||||
pub cached_conflicts: Vec<MMCComponentRequirement>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MMCComponentRequirement {
|
||||
pub uid: String,
|
||||
pub equals_version: Option<String>,
|
||||
pub suggests: Option<String>,
|
||||
}
|
||||
|
||||
// Checks if if its a folder, and the folder contains instance.cfg and mmc-pack.json, and they both parse
|
||||
#[tracing::instrument]
|
||||
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 fs::read_to_string(&mmc_pack).await {
|
||||
Ok(mmc_pack) => mmc_pack,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
load_instance_cfg(&instance_cfg).await.is_ok()
|
||||
&& serde_json::from_str::<MMCPack>(&mmc_pack).is_ok()
|
||||
}
|
||||
|
||||
// Loading the INI (instance.cfg) file
|
||||
async fn load_instance_cfg(file_path: &Path) -> crate::Result<MMCInstance> {
|
||||
let instance_cfg = io::read_to_string(file_path).await?;
|
||||
let instance_cfg_enum: MMCInstanceEnum =
|
||||
serde_ini::from_str::<MMCInstanceEnum>(&instance_cfg)?;
|
||||
match instance_cfg_enum {
|
||||
MMCInstanceEnum::General(instance_cfg) => Ok(instance_cfg.general),
|
||||
MMCInstanceEnum::Instance(instance_cfg) => Ok(instance_cfg),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn import_mmc(
|
||||
mmc_base_path: PathBuf, // path to base mmc folder
|
||||
instance_folder: String, // instance folder in mmc_base_path
|
||||
profile_path: ProfilePathId, // path to profile
|
||||
) -> crate::Result<()> {
|
||||
let mmc_instance_path = mmc_base_path
|
||||
.join("instances")
|
||||
.join(instance_folder.clone());
|
||||
|
||||
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 instance_cfg =
|
||||
load_instance_cfg(&mmc_instance_path.join("instance.cfg")).await?;
|
||||
|
||||
// Re-cache icon
|
||||
let icon = if let Some(icon_key) = instance_cfg.icon_key {
|
||||
let icon_path = mmc_base_path.join("icons").join(icon_key);
|
||||
import::recache_icon(icon_path).await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create description from instance.cfg
|
||||
let description = CreatePackDescription {
|
||||
icon,
|
||||
override_title: instance_cfg.name,
|
||||
project_id: instance_cfg.managed_pack_id,
|
||||
version_id: instance_cfg.managed_pack_version_id,
|
||||
existing_loading_bar: None,
|
||||
profile_path: profile_path.clone(),
|
||||
};
|
||||
|
||||
// Managed pack
|
||||
let backup_name = "Imported Modpack".to_string();
|
||||
|
||||
if instance_cfg.managed_pack.unwrap_or(false) {
|
||||
match instance_cfg.managed_pack_type {
|
||||
Some(MMCManagedPackType::Modrinth) => {
|
||||
// Modrinth Managed Pack
|
||||
// Kept separate as we may in the future want to add special handling for modrinth managed packs
|
||||
let backup_name = "Imported Modrinth Modpack".to_string();
|
||||
let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft");
|
||||
import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?;
|
||||
}
|
||||
Some(MMCManagedPackType::Flame) | Some(MMCManagedPackType::ATLauncher) => {
|
||||
// For flame/atlauncher managed packs
|
||||
// Treat as unmanaged, but with 'minecraft' folder instead of '.minecraft'
|
||||
let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join("minecraft");
|
||||
import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?;
|
||||
},
|
||||
Some(_) => {
|
||||
// For managed packs that aren't modrinth, flame, atlauncher
|
||||
// Treat as unmanaged
|
||||
let backup_name = "ImportedModpack".to_string();
|
||||
let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft");
|
||||
import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?;
|
||||
},
|
||||
_ => 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
|
||||
let backup_name = "Imported Modpack".to_string();
|
||||
let minecraft_folder = mmc_base_path
|
||||
.join("instances")
|
||||
.join(instance_folder)
|
||||
.join(".minecraft");
|
||||
import_mmc_unmanaged(
|
||||
profile_path,
|
||||
minecraft_folder,
|
||||
backup_name,
|
||||
description,
|
||||
mmc_pack,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn import_mmc_unmanaged(
|
||||
profile_path: ProfilePathId,
|
||||
minecraft_folder: PathBuf,
|
||||
backup_name: String,
|
||||
description: CreatePackDescription,
|
||||
mmc_pack: MMCPack,
|
||||
) -> crate::Result<()> {
|
||||
// Pack dependencies stored in mmc-pack.json, we convert to .mrpack pack dependencies
|
||||
let dependencies = mmc_pack
|
||||
.components
|
||||
.iter()
|
||||
.filter_map(|component| {
|
||||
if component.uid.starts_with("net.fabricmc.fabric-loader") {
|
||||
return Some((
|
||||
PackDependency::FabricLoader,
|
||||
component.version.clone().unwrap_or_default(),
|
||||
));
|
||||
}
|
||||
if component.uid.starts_with("net.minecraftforge") {
|
||||
return Some((
|
||||
PackDependency::Forge,
|
||||
component.version.clone().unwrap_or_default(),
|
||||
));
|
||||
}
|
||||
if component.uid.starts_with("org.quiltmc.quilt-loader") {
|
||||
return Some((
|
||||
PackDependency::QuiltLoader,
|
||||
component.version.clone().unwrap_or_default(),
|
||||
));
|
||||
}
|
||||
if component.uid.starts_with("net.minecraft") {
|
||||
return Some((
|
||||
PackDependency::Minecraft,
|
||||
component.version.clone().unwrap_or_default(),
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sets profile information to be that loaded from mmc-pack.json and instance.cfg
|
||||
install_from::set_profile_information(
|
||||
profile_path.clone(),
|
||||
&description,
|
||||
&backup_name,
|
||||
&dependencies,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc)
|
||||
copy_dotminecraft(profile_path.clone(), minecraft_folder).await?;
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(&profile_val, None).await?;
|
||||
|
||||
State::sync().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
268
theseus/src/api/pack/import/mod.rs
Normal file
268
theseus/src/api/pack/import/mod.rs
Normal file
@ -0,0 +1,268 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use io::IOError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
prelude::ProfilePathId,
|
||||
util::{fetch, io},
|
||||
};
|
||||
|
||||
pub mod atlauncher;
|
||||
pub mod curseforge;
|
||||
pub mod gdlauncher;
|
||||
pub mod mmc;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum ImportLauncherType {
|
||||
MultiMC,
|
||||
PrismLauncher,
|
||||
ATLauncher,
|
||||
GDLauncher,
|
||||
Curseforge,
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// Return a list of importable instances from a launcher type and base path, by iterating through the folder and checking
|
||||
pub async fn get_importable_instances(
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
// Some launchers have a different folder structure for instances
|
||||
let instances_folder = match launcher_type {
|
||||
ImportLauncherType::GDLauncher
|
||||
| ImportLauncherType::MultiMC
|
||||
| ImportLauncherType::PrismLauncher
|
||||
| ImportLauncherType::ATLauncher => base_path.join("instances"),
|
||||
ImportLauncherType::Curseforge => base_path.join("Instances"),
|
||||
ImportLauncherType::Unknown => {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Launcher type Unknown".to_string(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let mut instances = Vec::new();
|
||||
let mut dir = io::read_dir(&instances_folder).await?;
|
||||
while let Some(entry) = dir
|
||||
.next_entry()
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &instances_folder))?
|
||||
{
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
// Check instance is valid of this launcher type
|
||||
if is_valid_importable_instance(path.clone(), launcher_type).await {
|
||||
let name = path.file_name();
|
||||
if let Some(name) = name {
|
||||
instances.push(name.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(instances)
|
||||
}
|
||||
|
||||
// Import an instance from a launcher type and base path
|
||||
#[theseus_macros::debug_pin]
|
||||
#[tracing::instrument]
|
||||
pub async fn import_instance(
|
||||
profile_path: ProfilePathId,
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
instance_folder: String,
|
||||
) -> crate::Result<()> {
|
||||
tracing::debug!("Importing instance from {instance_folder}");
|
||||
match launcher_type {
|
||||
ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => {
|
||||
mmc::import_mmc(
|
||||
base_path, // path to base mmc folder
|
||||
instance_folder, // instance folder in mmc_base_path
|
||||
profile_path, // path to profile
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ImportLauncherType::ATLauncher => {
|
||||
atlauncher::import_atlauncher(
|
||||
base_path, // path to atlauncher folder
|
||||
instance_folder, // instance folder in atlauncher
|
||||
profile_path, // path to profile
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ImportLauncherType::GDLauncher => {
|
||||
gdlauncher::import_gdlauncher(
|
||||
base_path.join("instances").join(instance_folder), // path to gdlauncher folder
|
||||
profile_path, // path to profile
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ImportLauncherType::Curseforge => {
|
||||
curseforge::import_curseforge(
|
||||
base_path.join("Instances").join(instance_folder), // path to curseforge folder
|
||||
profile_path, // path to profile
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ImportLauncherType::Unknown => {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Launcher type Unknown".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
tracing::debug!("Completed import.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the default path for the given launcher type
|
||||
/// None if it can't be found or doesn't exist
|
||||
pub fn get_default_launcher_path(
|
||||
r#type: ImportLauncherType,
|
||||
) -> Option<PathBuf> {
|
||||
let path = match r#type {
|
||||
ImportLauncherType::MultiMC => None, // multimc data is *in* app dir
|
||||
ImportLauncherType::PrismLauncher => {
|
||||
Some(dirs::data_dir()?.join("PrismLauncher"))
|
||||
}
|
||||
ImportLauncherType::ATLauncher => {
|
||||
Some(dirs::data_dir()?.join("ATLauncher"))
|
||||
}
|
||||
ImportLauncherType::GDLauncher => {
|
||||
Some(dirs::data_dir()?.join("gdlauncher_next"))
|
||||
}
|
||||
ImportLauncherType::Curseforge => {
|
||||
Some(dirs::home_dir()?.join("curseforge").join("minecraft"))
|
||||
}
|
||||
ImportLauncherType::Unknown => None,
|
||||
};
|
||||
let path = path?;
|
||||
if path.exists() {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if this PathBuf is a valid instance for the given launcher type
|
||||
#[theseus_macros::debug_pin]
|
||||
#[tracing::instrument]
|
||||
pub async fn is_valid_importable_instance(
|
||||
instance_path: PathBuf,
|
||||
r#type: ImportLauncherType,
|
||||
) -> bool {
|
||||
match r#type {
|
||||
ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => {
|
||||
mmc::is_valid_mmc(instance_path).await
|
||||
}
|
||||
ImportLauncherType::ATLauncher => {
|
||||
atlauncher::is_valid_atlauncher(instance_path).await
|
||||
}
|
||||
ImportLauncherType::GDLauncher => {
|
||||
gdlauncher::is_valid_gdlauncher(instance_path).await
|
||||
}
|
||||
ImportLauncherType::Curseforge => {
|
||||
curseforge::is_valid_curseforge(instance_path).await
|
||||
}
|
||||
ImportLauncherType::Unknown => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches an image file in the filesystem into the cache directory, and returns the path to the cached file.
|
||||
#[theseus_macros::debug_pin]
|
||||
#[tracing::instrument]
|
||||
pub async fn recache_icon(
|
||||
icon_path: PathBuf,
|
||||
) -> crate::Result<Option<PathBuf>> {
|
||||
let state = crate::State::get().await?;
|
||||
|
||||
let bytes = tokio::fs::read(&icon_path).await;
|
||||
if let Ok(bytes) = bytes {
|
||||
let bytes = bytes::Bytes::from(bytes);
|
||||
let cache_dir = &state.directories.caches_dir();
|
||||
let semaphore = &state.io_semaphore;
|
||||
Ok(Some(
|
||||
fetch::write_cached_icon(
|
||||
&icon_path.to_string_lossy(),
|
||||
cache_dir,
|
||||
bytes,
|
||||
semaphore,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
} else {
|
||||
// could not find icon (for instance, prism default icon, etc)
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn copy_dotminecraft(
|
||||
profile_path: ProfilePathId,
|
||||
dotminecraft: PathBuf,
|
||||
) -> crate::Result<()> {
|
||||
// Get full path to profile
|
||||
let profile_path = profile_path.get_full_path().await?;
|
||||
|
||||
// std fs copy every file in dotminecraft to profile_path
|
||||
let mut dir = io::read_dir(&dotminecraft).await?;
|
||||
while let Some(entry) = dir
|
||||
.next_entry()
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &dotminecraft))?
|
||||
{
|
||||
let path = entry.path();
|
||||
copy_dir_to(
|
||||
&path,
|
||||
&profile_path.join(path.file_name().ok_or_else(|| {
|
||||
crate::ErrorKind::InputError(format!(
|
||||
"Invalid file: {}",
|
||||
&path.display()
|
||||
))
|
||||
})?),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recursively fs::copy every file in src to dest
|
||||
/// uses async recursion
|
||||
#[theseus_macros::debug_pin]
|
||||
#[async_recursion::async_recursion]
|
||||
#[tracing::instrument]
|
||||
async fn copy_dir_to(src: &Path, dst: &Path) -> crate::Result<()> {
|
||||
if !src.is_dir() {
|
||||
io::copy(src, dst).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create the destination directory
|
||||
io::create_dir_all(&dst).await?;
|
||||
|
||||
// Iterate over the directory
|
||||
let mut dir = io::read_dir(&src).await?;
|
||||
while let Some(child) = dir
|
||||
.next_entry()
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, src))?
|
||||
{
|
||||
let src_child = child.path();
|
||||
let dst_child = dst.join(src_child.file_name().ok_or_else(|| {
|
||||
crate::ErrorKind::InputError(format!(
|
||||
"Invalid file: {}",
|
||||
&src_child.display()
|
||||
))
|
||||
})?);
|
||||
|
||||
if src_child.is_dir() {
|
||||
// Recurse into sub-directory
|
||||
copy_dir_to(&src_child, &dst_child).await?;
|
||||
} else {
|
||||
// Copy file
|
||||
io::copy(&src_child, &dst_child).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -2,8 +2,9 @@ use crate::config::MODRINTH_API_URL;
|
||||
use crate::data::ModLoader;
|
||||
use crate::event::emit::{emit_loading, init_loading};
|
||||
use crate::event::{LoadingBarId, LoadingBarType};
|
||||
use crate::prelude::ProfilePathId;
|
||||
use crate::state::{
|
||||
LinkedData, ModrinthProject, ModrinthVersion, ProfilePathId, SideType,
|
||||
LinkedData, ModrinthProject, ModrinthVersion, ProfileInstallStage, SideType,
|
||||
};
|
||||
use crate::util::fetch::{
|
||||
fetch, fetch_advanced, fetch_json, write_cached_icon,
|
||||
@ -64,7 +65,7 @@ pub enum EnvType {
|
||||
Server,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PackDependency {
|
||||
Forge,
|
||||
@ -76,12 +77,14 @@ pub enum PackDependency {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
pub enum CreatePackLocation {
|
||||
// Create a pack from a modrinth version ID (such as a modpack)
|
||||
FromVersionId {
|
||||
project_id: String,
|
||||
version_id: String,
|
||||
title: String,
|
||||
icon_url: Option<String>,
|
||||
},
|
||||
// Create a pack from a file (such as an .mrpack for installing from a file, or a folder name for importing)
|
||||
FromFile {
|
||||
path: PathBuf,
|
||||
},
|
||||
@ -100,9 +103,29 @@ pub struct CreatePackProfile {
|
||||
pub skip_install_profile: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CreatePackDescription {
|
||||
// default
|
||||
impl Default for CreatePackProfile {
|
||||
fn default() -> Self {
|
||||
CreatePackProfile {
|
||||
name: "Untitled".to_string(),
|
||||
game_version: "1.19.4".to_string(),
|
||||
modloader: ModLoader::Vanilla,
|
||||
loader_version: None,
|
||||
icon: None,
|
||||
icon_url: None,
|
||||
linked_data: None,
|
||||
skip_install_profile: Some(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CreatePack {
|
||||
pub file: bytes::Bytes,
|
||||
pub description: CreatePackDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CreatePackDescription {
|
||||
pub icon: Option<PathBuf>,
|
||||
pub override_title: Option<String>,
|
||||
pub project_id: Option<String>,
|
||||
@ -122,16 +145,12 @@ pub fn get_profile_from_pack(
|
||||
icon_url,
|
||||
} => CreatePackProfile {
|
||||
name: title,
|
||||
game_version: "1.19.4".to_string(),
|
||||
modloader: ModLoader::Vanilla,
|
||||
loader_version: None,
|
||||
icon: None,
|
||||
icon_url,
|
||||
linked_data: Some(LinkedData {
|
||||
project_id: Some(project_id),
|
||||
version_id: Some(version_id),
|
||||
}),
|
||||
skip_install_profile: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
CreatePackLocation::FromFile { path } => {
|
||||
let file_name = path
|
||||
@ -142,13 +161,7 @@ pub fn get_profile_from_pack(
|
||||
|
||||
CreatePackProfile {
|
||||
name: file_name,
|
||||
game_version: "1.19.4".to_string(),
|
||||
modloader: ModLoader::Vanilla,
|
||||
loader_version: None,
|
||||
icon: None,
|
||||
icon_url: None,
|
||||
linked_data: None,
|
||||
skip_install_profile: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -162,7 +175,7 @@ pub async fn generate_pack_from_version_id(
|
||||
title: String,
|
||||
icon_url: Option<String>,
|
||||
profile_path: ProfilePathId,
|
||||
) -> crate::Result<CreatePackDescription> {
|
||||
) -> crate::Result<CreatePack> {
|
||||
let state = State::get().await?;
|
||||
|
||||
let loading_bar = init_loading(
|
||||
@ -249,14 +262,16 @@ pub async fn generate_pack_from_version_id(
|
||||
};
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
Ok(CreatePackDescription {
|
||||
Ok(CreatePack {
|
||||
file,
|
||||
description: CreatePackDescription {
|
||||
icon,
|
||||
override_title: None,
|
||||
project_id: Some(project_id),
|
||||
version_id: Some(version_id),
|
||||
existing_loading_bar: Some(loading_bar),
|
||||
profile_path,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -265,15 +280,90 @@ pub async fn generate_pack_from_version_id(
|
||||
pub async fn generate_pack_from_file(
|
||||
path: PathBuf,
|
||||
profile_path: ProfilePathId,
|
||||
) -> crate::Result<CreatePackDescription> {
|
||||
) -> crate::Result<CreatePack> {
|
||||
let file = io::read(&path).await?;
|
||||
Ok(CreatePackDescription {
|
||||
Ok(CreatePack {
|
||||
file: bytes::Bytes::from(file),
|
||||
description: CreatePackDescription {
|
||||
icon: None,
|
||||
override_title: None,
|
||||
project_id: None,
|
||||
version_id: None,
|
||||
existing_loading_bar: None,
|
||||
profile_path,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets generated profile attributes to the pack ones (using profile::edit)
|
||||
/// This includes the pack name, icon, game version, loader version, and loader
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn set_profile_information(
|
||||
profile_path: ProfilePathId,
|
||||
description: &CreatePackDescription,
|
||||
backup_name: &str,
|
||||
dependencies: &HashMap<PackDependency, String>,
|
||||
) -> crate::Result<()> {
|
||||
let mut game_version: Option<&String> = None;
|
||||
let mut mod_loader = None;
|
||||
let mut loader_version = None;
|
||||
|
||||
for (key, value) in dependencies {
|
||||
match key {
|
||||
PackDependency::Forge => {
|
||||
mod_loader = Some(ModLoader::Forge);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::FabricLoader => {
|
||||
mod_loader = Some(ModLoader::Fabric);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::QuiltLoader => {
|
||||
mod_loader = Some(ModLoader::Quilt);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::Minecraft => game_version = Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
let game_version = if let Some(game_version) = game_version {
|
||||
game_version
|
||||
} else {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Pack did not specify Minecraft version".to_string(),
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla);
|
||||
let loader_version = if mod_loader != ModLoader::Vanilla {
|
||||
crate::profile_create::get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
mod_loader,
|
||||
loader_version.cloned(),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Sets values in profile
|
||||
crate::api::profile::edit(&profile_path, |prof| {
|
||||
prof.metadata.name = description
|
||||
.override_title
|
||||
.clone()
|
||||
.unwrap_or_else(|| backup_name.to_string());
|
||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||
prof.metadata.linked_data = Some(LinkedData {
|
||||
project_id: description.project_id.clone(),
|
||||
version_id: description.version_id.clone(),
|
||||
});
|
||||
prof.metadata.icon = description.icon.clone();
|
||||
prof.metadata.game_version = game_version.clone();
|
||||
prof.metadata.loader_version = loader_version.clone();
|
||||
prof.metadata.loader = mod_loader;
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
use crate::data::ModLoader;
|
||||
use crate::event::emit::{
|
||||
emit_loading, init_or_edit_loading, loading_try_for_each_concurrent,
|
||||
};
|
||||
use crate::event::LoadingBarType;
|
||||
use crate::pack::install_from::{EnvType, PackFile, PackFileHash};
|
||||
use crate::state::{LinkedData, ProfileInstallStage, ProfilePathId, SideType};
|
||||
use crate::pack::install_from::{
|
||||
set_profile_information, EnvType, PackFile, PackFileHash,
|
||||
};
|
||||
use crate::prelude::ProfilePathId;
|
||||
use crate::state::SideType;
|
||||
use crate::util::fetch::{fetch_mirrors, write};
|
||||
use crate::State;
|
||||
use async_zip::tokio::read::seek::ZipFileReader;
|
||||
@ -13,17 +15,18 @@ use std::io::Cursor;
|
||||
use std::path::{Component, PathBuf};
|
||||
|
||||
use super::install_from::{
|
||||
generate_pack_from_file, generate_pack_from_version_id,
|
||||
CreatePackDescription, CreatePackLocation, PackDependency, PackFormat,
|
||||
generate_pack_from_file, generate_pack_from_version_id, CreatePack,
|
||||
CreatePackLocation, PackFormat,
|
||||
};
|
||||
|
||||
/// Install a modpack from a mrpack file (a modrinth .zip format)
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn install_pack(
|
||||
pub async fn install_zipped_mrpack(
|
||||
location: CreatePackLocation,
|
||||
profile_path: ProfilePathId,
|
||||
profile: ProfilePathId,
|
||||
) -> crate::Result<ProfilePathId> {
|
||||
// Get file from description
|
||||
let description: CreatePackDescription = match location {
|
||||
let create_pack: CreatePack = match location {
|
||||
CreatePackLocation::FromVersionId {
|
||||
project_id,
|
||||
version_id,
|
||||
@ -31,26 +34,22 @@ pub async fn install_pack(
|
||||
icon_url,
|
||||
} => {
|
||||
generate_pack_from_version_id(
|
||||
project_id,
|
||||
version_id,
|
||||
title,
|
||||
icon_url,
|
||||
profile_path,
|
||||
project_id, version_id, title, icon_url, profile,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
CreatePackLocation::FromFile { path } => {
|
||||
generate_pack_from_file(path, profile_path).await?
|
||||
generate_pack_from_file(path, profile).await?
|
||||
}
|
||||
};
|
||||
|
||||
let file = description.file;
|
||||
let icon = description.icon;
|
||||
let override_title = description.override_title;
|
||||
let project_id = description.project_id;
|
||||
let version_id = description.version_id;
|
||||
let existing_loading_bar = description.existing_loading_bar;
|
||||
let profile_path = description.profile_path;
|
||||
let file = create_pack.file;
|
||||
let description = create_pack.description.clone(); // make a copy for profile edit function
|
||||
let icon = create_pack.description.icon;
|
||||
let project_id = create_pack.description.project_id;
|
||||
let version_id = create_pack.description.version_id;
|
||||
let existing_loading_bar = create_pack.description.existing_loading_bar;
|
||||
let profile = create_pack.description.profile_path;
|
||||
|
||||
let state = &State::get().await?;
|
||||
|
||||
@ -92,69 +91,22 @@ pub async fn install_pack(
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut game_version = None;
|
||||
let mut mod_loader = None;
|
||||
let mut loader_version = None;
|
||||
for (key, value) in &pack.dependencies {
|
||||
match key {
|
||||
PackDependency::Forge => {
|
||||
mod_loader = Some(ModLoader::Forge);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::FabricLoader => {
|
||||
mod_loader = Some(ModLoader::Fabric);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::QuiltLoader => {
|
||||
mod_loader = Some(ModLoader::Quilt);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::Minecraft => game_version = Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
let game_version = if let Some(game_version) = game_version {
|
||||
game_version
|
||||
} else {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Pack did not specify Minecraft version".to_string(),
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
let loader_version =
|
||||
crate::profile_create::get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
mod_loader.unwrap_or(ModLoader::Vanilla),
|
||||
loader_version.cloned(),
|
||||
// Sets generated profile attributes to the pack ones (using profile::edit)
|
||||
set_profile_information(
|
||||
profile.clone(),
|
||||
&description,
|
||||
&pack.name,
|
||||
&pack.dependencies,
|
||||
)
|
||||
.await?;
|
||||
crate::api::profile::edit(&profile_path, |prof| {
|
||||
prof.metadata.name =
|
||||
override_title.clone().unwrap_or_else(|| pack.name.clone());
|
||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||
prof.metadata.linked_data = Some(LinkedData {
|
||||
project_id: project_id.clone(),
|
||||
version_id: version_id.clone(),
|
||||
});
|
||||
prof.metadata.icon = icon.clone();
|
||||
prof.metadata.game_version = game_version.clone();
|
||||
prof.metadata.loader_version = loader_version.clone();
|
||||
prof.metadata.loader = mod_loader.unwrap_or(ModLoader::Vanilla);
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
|
||||
let profile_path = profile_path.clone();
|
||||
let profile_full_path = profile.get_full_path().await?;
|
||||
let profile = profile.clone();
|
||||
let result = async {
|
||||
let loading_bar = init_or_edit_loading(
|
||||
existing_loading_bar,
|
||||
LoadingBarType::PackDownload {
|
||||
profile_path: profile_path
|
||||
.get_full_path()
|
||||
.await?
|
||||
.clone(),
|
||||
profile_path: profile_full_path.clone(),
|
||||
pack_name: pack.name.clone(),
|
||||
icon,
|
||||
pack_id: project_id,
|
||||
@ -176,7 +128,7 @@ pub async fn install_pack(
|
||||
num_files,
|
||||
None,
|
||||
|project| {
|
||||
let profile_path = profile_path.clone();
|
||||
let profile_full_path = profile_full_path.clone();
|
||||
async move {
|
||||
//TODO: Future update: prompt user for optional files in a modpack
|
||||
if let Some(env) = project.env {
|
||||
@ -210,9 +162,7 @@ pub async fn install_pack(
|
||||
match path {
|
||||
Component::CurDir
|
||||
| Component::Normal(_) => {
|
||||
let path = profile_path
|
||||
.get_full_path()
|
||||
.await?
|
||||
let path = profile_full_path
|
||||
.join(project.path);
|
||||
write(
|
||||
&path,
|
||||
@ -275,10 +225,7 @@ pub async fn install_pack(
|
||||
|
||||
if new_path.file_name().is_some() {
|
||||
write(
|
||||
&profile_path
|
||||
.get_full_path()
|
||||
.await?
|
||||
.join(new_path),
|
||||
&profile_full_path.join(new_path),
|
||||
&content,
|
||||
&state.io_semaphore,
|
||||
)
|
||||
@ -298,7 +245,7 @@ pub async fn install_pack(
|
||||
}
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile_path, None).await?
|
||||
crate::api::profile::get(&profile, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(
|
||||
&profile_val,
|
||||
@ -309,14 +256,14 @@ pub async fn install_pack(
|
||||
State::sync().await?;
|
||||
}
|
||||
|
||||
Ok::<ProfilePathId, crate::Error>(profile_path.clone())
|
||||
Ok::<ProfilePathId, crate::Error>(profile.clone())
|
||||
}
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(err) => {
|
||||
let _ = crate::api::profile::remove(&profile_path).await;
|
||||
let _ = crate::api::profile::remove(&profile).await;
|
||||
|
||||
Err(err)
|
||||
}
|
||||
@ -332,7 +279,7 @@ pub async fn install_pack(
|
||||
match result {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(err) => {
|
||||
let _ = crate::api::profile::remove(&profile_path).await;
|
||||
let _ = crate::api::profile::remove(&profile).await;
|
||||
|
||||
Err(err)
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
pub mod install;
|
||||
pub mod import;
|
||||
pub mod install_from;
|
||||
pub mod install_mrpack;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
//! Theseus profile management interface
|
||||
use crate::state::{LinkedData, ProfilePathId};
|
||||
use crate::pack::install_from::CreatePackProfile;
|
||||
use crate::prelude::ProfilePathId;
|
||||
use crate::state::LinkedData;
|
||||
use crate::util::io::{self, canonicalize};
|
||||
use crate::{
|
||||
event::{emit::emit_profile, ProfilePayloadType},
|
||||
@ -128,6 +130,22 @@ pub async fn profile_create(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn profile_create_from_creator(
|
||||
profile: CreatePackProfile,
|
||||
) -> crate::Result<ProfilePathId> {
|
||||
profile_create(
|
||||
profile.name,
|
||||
profile.game_version,
|
||||
profile.modloader,
|
||||
profile.loader_version,
|
||||
profile.icon,
|
||||
profile.icon_url,
|
||||
profile.linked_data,
|
||||
profile.skip_install_profile,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub(crate) async fn get_loader_version_from_loader(
|
||||
|
||||
@ -7,6 +7,9 @@ pub enum ErrorKind {
|
||||
#[error("Filesystem error: {0}")]
|
||||
FSError(String),
|
||||
|
||||
#[error("Serialization error (INI): {0}")]
|
||||
INIError(#[from] serde_ini::de::Error),
|
||||
|
||||
#[error("Serialization error (JSON): {0}")]
|
||||
JSONError(#[from] serde_json::Error),
|
||||
|
||||
|
||||
@ -93,7 +93,7 @@ pub struct LoadingBar {
|
||||
pub cli_progress_bar: indicatif::ProgressBar,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct LoadingBarId(Uuid);
|
||||
|
||||
// When Loading bar id is dropped, we should remove it from the hashmap
|
||||
|
||||
@ -135,6 +135,21 @@ pub async fn rename(
|
||||
})
|
||||
}
|
||||
|
||||
// copy
|
||||
pub async fn copy(
|
||||
from: impl AsRef<std::path::Path>,
|
||||
to: impl AsRef<std::path::Path>,
|
||||
) -> Result<u64, IOError> {
|
||||
let from = from.as_ref();
|
||||
let to = to.as_ref();
|
||||
tokio::fs::copy(from, to)
|
||||
.await
|
||||
.map_err(|e| IOError::IOPathError {
|
||||
source: e,
|
||||
path: from.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
// remove file
|
||||
pub async fn remove_file(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
|
||||
70
theseus_gui/src-tauri/src/api/import.rs
Normal file
70
theseus_gui/src-tauri/src/api/import.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::api::Result;
|
||||
use theseus::pack::import::ImportLauncherType;
|
||||
|
||||
use theseus::pack::import;
|
||||
use theseus::prelude::ProfilePathId;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("import")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
import_get_importable_instances,
|
||||
import_import_instance,
|
||||
import_is_valid_importable_instance,
|
||||
import_get_default_launcher_path,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Gets a list of importable instances from a launcher type and base path
|
||||
/// eg: get_importable_instances(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"))
|
||||
/// returns ["Instance 1", "Instance 2"]
|
||||
#[tauri::command]
|
||||
pub async fn import_get_importable_instances(
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
) -> Result<Vec<String>> {
|
||||
Ok(import::get_importable_instances(launcher_type, base_path).await?)
|
||||
}
|
||||
|
||||
/// Import an instance from a launcher type and base path
|
||||
/// eg: import_instance(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"), "Instance 1")
|
||||
#[tauri::command]
|
||||
pub async fn import_import_instance(
|
||||
profile_path: ProfilePathId,
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
instance_folder: String,
|
||||
) -> Result<()> {
|
||||
import::import_instance(
|
||||
profile_path,
|
||||
launcher_type,
|
||||
base_path,
|
||||
instance_folder,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if this instance is valid for importing, given a certain launcher type
|
||||
/// eg: is_valid_importable_instance(PathBuf::from("C:/MultiMC/Instance 1"), ImportLauncherType::MultiMC)
|
||||
#[tauri::command]
|
||||
pub async fn import_is_valid_importable_instance(
|
||||
instance_folder: PathBuf,
|
||||
launcher_type: ImportLauncherType,
|
||||
) -> Result<bool> {
|
||||
Ok(
|
||||
import::is_valid_importable_instance(instance_folder, launcher_type)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the default path for the given launcher type
|
||||
/// None if it can't be found or doesn't exist
|
||||
#[tauri::command]
|
||||
pub async fn import_get_default_launcher_path(
|
||||
launcher_type: ImportLauncherType,
|
||||
) -> Result<Option<PathBuf>> {
|
||||
Ok(import::get_default_launcher_path(launcher_type))
|
||||
}
|
||||
@ -3,6 +3,7 @@ use serde::{Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod auth;
|
||||
pub mod import;
|
||||
pub mod jre;
|
||||
pub mod logs;
|
||||
pub mod metadata;
|
||||
|
||||
@ -2,8 +2,8 @@ use crate::api::Result;
|
||||
|
||||
use theseus::{
|
||||
pack::{
|
||||
install::install_pack,
|
||||
install_from::{CreatePackLocation, CreatePackProfile},
|
||||
install_mrpack::install_zipped_mrpack,
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
@ -22,7 +22,7 @@ pub async fn pack_install(
|
||||
location: CreatePackLocation,
|
||||
profile: ProfilePathId,
|
||||
) -> Result<ProfilePathId> {
|
||||
Ok(install_pack(location, profile).await?)
|
||||
Ok(install_zipped_mrpack(location, profile).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@ -118,6 +118,7 @@ fn main() {
|
||||
}
|
||||
let builder = builder
|
||||
.plugin(api::auth::init())
|
||||
.plugin(api::import::init())
|
||||
.plugin(api::logs::init())
|
||||
.plugin(api::jre::init())
|
||||
.plugin(api::metadata::init())
|
||||
|
||||
61
theseus_gui/src/helpers/import.js
Normal file
61
theseus_gui/src/helpers/import.js
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* All theseus API calls return serialized values (both return values and errors);
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { create } from './profile'
|
||||
|
||||
/*
|
||||
API for importing instances from other launchers
|
||||
launcherType can be one of the following:
|
||||
- MultiMC
|
||||
- GDLauncher
|
||||
- ATLauncher
|
||||
- Curseforge
|
||||
- PrismLauncher
|
||||
- Unknown (shouldn't be used, but is used internally if the launcher type isn't recognized)
|
||||
|
||||
For each launcher type, we can get a guess of the default path for the launcher, and a list of importable instances
|
||||
For most launchers, this will be the application's data directory, with two exceptions:
|
||||
- MultiMC: this goes to the app directory (wherever the app is)
|
||||
- Curseforge: this goes to the 'minecraft' subdirectory of the data directory, as Curseforge has multiple games
|
||||
|
||||
*/
|
||||
|
||||
/// Gets a list of importable instances from a launcher type and base path
|
||||
/// eg: get_importable_instances("MultiMC", "C:/MultiMC")
|
||||
/// returns ["Instance 1", "Instance 2"]
|
||||
export async function get_importable_instances(launcherType, basePath) {
|
||||
return await invoke('plugin:import|import_get_importable_instances', { launcherType, basePath })
|
||||
}
|
||||
|
||||
/// Import an instance from a launcher type and base path
|
||||
/// eg: import_instance("profile-name-to-go-to", "MultiMC", "C:/MultiMC", "Instance 1")
|
||||
export async function import_instance(launcherType, basePath, instanceFolder) {
|
||||
// create a basic, empty instance (most properties will be filled in by the import process)
|
||||
const profilePath = await create(instanceFolder, '1.19.4', 'vanilla', 'latest', null)
|
||||
|
||||
return await invoke('plugin:import|import_import_instance', {
|
||||
profilePath,
|
||||
launcherType,
|
||||
basePath,
|
||||
instanceFolder,
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if this instance is valid for importing, given a certain launcher type
|
||||
/// eg: is_valid_importable_instance("C:/MultiMC/Instance 1", "MultiMC")
|
||||
export async function is_valid_importable_instance(instanceFolder, launcherType) {
|
||||
return await invoke('plugin:import|import_is_valid_importable_instance', {
|
||||
instanceFolder,
|
||||
launcherType,
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the default path for the given launcher type
|
||||
/// null if it can't be found or doesn't exist
|
||||
/// eg: get_default_launcher_path("MultiMC")
|
||||
export async function get_default_launcher_path(launcherType) {
|
||||
return await invoke('plugin:import|import_get_default_launcher_path', { launcherType })
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user