Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profile imports #348

Merged
merged 12 commits into from
Jul 22, 2023
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@
"rust-analyzer.linkedProjects": [
"./theseus/Cargo.toml"
],
"rust-analyzer.showUnlinkedFileNotification": false,
}
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions theseus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
244 changes: 244 additions & 0 deletions theseus/src/api/pack/import/atlauncher.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
Loading
Loading