2024-09-01 15:06:21 +01:00
|
|
|
use crate::{
|
|
|
|
file_meta::{get_normalized_relative_path, FileMeta},
|
|
|
|
mod_meta::{ModMeta, ModProvider},
|
|
|
|
};
|
2024-08-24 13:03:27 +01:00
|
|
|
use anyhow::Result;
|
2024-08-19 00:50:38 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::{
|
2024-09-01 11:57:58 +01:00
|
|
|
collections::{BTreeMap, BTreeSet},
|
2024-08-20 01:20:17 +01:00
|
|
|
path::{Path, PathBuf},
|
2024-08-19 00:50:38 +01:00
|
|
|
};
|
2024-08-14 20:51:42 +01:00
|
|
|
|
2024-08-16 00:31:02 +01:00
|
|
|
const MODPACK_FILENAME: &str = "modpack.toml";
|
2024-08-13 23:56:57 +01:00
|
|
|
|
2024-08-18 01:16:23 +01:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
2024-08-13 23:56:57 +01:00
|
|
|
pub enum ModLoader {
|
|
|
|
Forge,
|
|
|
|
Fabric,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToString for ModLoader {
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
match self {
|
|
|
|
ModLoader::Forge => "Forge",
|
|
|
|
ModLoader::Fabric => "Fabric",
|
|
|
|
}
|
|
|
|
.into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::str::FromStr for ModLoader {
|
2024-08-24 13:03:27 +01:00
|
|
|
type Err = anyhow::Error;
|
2024-08-13 23:56:57 +01:00
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
match s {
|
|
|
|
"Fabric" => Ok(Self::Fabric),
|
|
|
|
"Forge" => Ok(Self::Forge),
|
2024-08-24 13:03:27 +01:00
|
|
|
_ => anyhow::bail!("Invalid mod launcher: {}", s),
|
2024-08-13 23:56:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-17 16:11:04 +01:00
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
2024-08-13 23:56:57 +01:00
|
|
|
pub struct ModpackMeta {
|
2024-09-01 15:06:21 +01:00
|
|
|
/// The name of the modpack
|
2024-08-20 01:20:17 +01:00
|
|
|
pub pack_name: String,
|
2024-09-01 15:06:21 +01:00
|
|
|
/// The intended minecraft version on which this pack should run
|
2024-08-17 16:11:04 +01:00
|
|
|
pub mc_version: String,
|
2024-09-01 15:06:21 +01:00
|
|
|
/// The default modloader for the modpack
|
2024-08-17 16:11:04 +01:00
|
|
|
pub modloader: ModLoader,
|
2024-09-01 15:06:21 +01:00
|
|
|
/// Map of mod name -> mod metadata
|
2024-09-01 10:53:38 +01:00
|
|
|
pub mods: BTreeMap<String, ModMeta>,
|
2024-09-01 15:06:21 +01:00
|
|
|
/// Mapping of relative paths to files to copy over from the modpack
|
|
|
|
pub files: Option<BTreeMap<String, FileMeta>>,
|
|
|
|
/// Default provider for newly added mods in the modpack
|
2024-08-17 16:11:04 +01:00
|
|
|
pub default_providers: Vec<ModProvider>,
|
2024-09-01 15:06:21 +01:00
|
|
|
/// A set of forbidden mods in the modpack
|
2024-09-01 11:57:58 +01:00
|
|
|
pub forbidden_mods: BTreeSet<String>,
|
2024-08-14 20:51:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ModpackMeta {
|
2024-08-14 21:28:40 +01:00
|
|
|
pub fn new(pack_name: &str, mc_version: &str, modloader: ModLoader) -> Self {
|
2024-08-14 20:51:42 +01:00
|
|
|
Self {
|
2024-08-14 21:28:40 +01:00
|
|
|
pack_name: pack_name.into(),
|
2024-08-14 20:51:42 +01:00
|
|
|
mc_version: mc_version.into(),
|
|
|
|
modloader: modloader,
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-01 10:53:38 +01:00
|
|
|
pub fn iter_mods(&self) -> std::collections::btree_map::Values<String, ModMeta> {
|
2024-08-16 00:31:02 +01:00
|
|
|
self.mods.values().into_iter()
|
|
|
|
}
|
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn load_from_directory(directory: &Path) -> Result<Self> {
|
2024-08-20 01:20:17 +01:00
|
|
|
let modpack_meta_file_path = directory.join(PathBuf::from(MODPACK_FILENAME));
|
2024-08-14 22:32:45 +01:00
|
|
|
if !modpack_meta_file_path.exists() {
|
2024-08-24 13:03:27 +01:00
|
|
|
anyhow::bail!(
|
2024-08-14 22:32:45 +01:00
|
|
|
"Directory '{}' does not seem to be a valid modpack project directory.",
|
|
|
|
directory.display()
|
|
|
|
)
|
|
|
|
};
|
|
|
|
let modpack_contents = std::fs::read_to_string(modpack_meta_file_path)?;
|
|
|
|
Ok(toml::from_str(&modpack_contents)?)
|
|
|
|
}
|
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn load_from_current_directory() -> Result<Self> {
|
2024-08-14 22:32:45 +01:00
|
|
|
Self::load_from_directory(&std::env::current_dir()?)
|
|
|
|
}
|
|
|
|
|
2024-08-14 20:51:42 +01:00
|
|
|
pub fn provider(mut self, provider: ModProvider) -> Self {
|
|
|
|
if !self.default_providers.contains(&provider) {
|
|
|
|
self.default_providers.push(provider);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn add_mod(mut self, mod_meta: &ModMeta) -> Result<Self> {
|
2024-08-19 00:50:38 +01:00
|
|
|
if self.forbidden_mods.contains(&mod_meta.name) {
|
2024-08-24 13:03:27 +01:00
|
|
|
anyhow::bail!("Cannot add forbidden mod {} to modpack", mod_meta.name)
|
2024-08-19 00:50:38 +01:00
|
|
|
} else {
|
|
|
|
self.mods
|
|
|
|
.insert(mod_meta.name.to_string(), mod_meta.clone());
|
|
|
|
}
|
|
|
|
Ok(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn forbid_mod(&mut self, mod_name: &str) {
|
|
|
|
self.forbidden_mods.insert(mod_name.into());
|
|
|
|
println!("Mod {} has been forbidden from the modpack", mod_name);
|
2024-08-14 20:51:42 +01:00
|
|
|
}
|
2024-08-14 21:28:40 +01:00
|
|
|
|
2024-08-17 23:38:40 +01:00
|
|
|
pub fn remove_mod(mut self, mod_name: &str) -> Self {
|
|
|
|
self.mods.remove(mod_name);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-09-01 15:06:21 +01:00
|
|
|
/// Add local files or folders to the pack. These should be committed to version control
|
|
|
|
pub fn add_file(
|
|
|
|
&mut self,
|
|
|
|
file_path: &Path,
|
|
|
|
file_meta: &FileMeta,
|
|
|
|
pack_root: &Path,
|
|
|
|
) -> Result<&mut Self> {
|
|
|
|
let relative_path = if file_path.is_relative() {
|
|
|
|
file_path
|
|
|
|
} else {
|
|
|
|
&pathdiff::diff_paths(file_path, pack_root).ok_or(anyhow::format_err!(
|
|
|
|
"Cannot get relative path of {} in {}",
|
|
|
|
file_path.display(),
|
|
|
|
pack_root.display()
|
|
|
|
))?
|
|
|
|
};
|
|
|
|
|
|
|
|
let target_path = PathBuf::from(&file_meta.target_path);
|
|
|
|
if !target_path.is_relative() {
|
|
|
|
anyhow::bail!(
|
|
|
|
"Target path {} for file {} is not relative!",
|
|
|
|
file_meta.target_path,
|
|
|
|
file_path.display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let full_path = pack_root.join(relative_path);
|
|
|
|
|
|
|
|
// Make sure this path is consistent across platforms
|
|
|
|
let relative_path = get_normalized_relative_path(relative_path, &pack_root)?;
|
|
|
|
|
|
|
|
if !full_path
|
|
|
|
.canonicalize()?
|
|
|
|
.starts_with(pack_root.canonicalize()?)
|
|
|
|
{
|
|
|
|
anyhow::bail!(
|
|
|
|
"You cannot add local files to the modpack from outside the pack source directory. {} is not contained in {}",
|
|
|
|
full_path.canonicalize()?.display(),
|
|
|
|
pack_root.canonicalize()?.display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
match &mut self.files {
|
|
|
|
Some(files) => {
|
|
|
|
files.insert(relative_path.clone(), file_meta.clone());
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
self.files
|
|
|
|
.insert(BTreeMap::new())
|
|
|
|
.insert(relative_path.clone(), file_meta.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
println!(
|
|
|
|
"Added file '{relative_path}' -> '{}' to modpack...",
|
|
|
|
file_meta.target_path
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_file(&mut self, file_path: &PathBuf, pack_root: &Path) -> Result<&mut Self> {
|
|
|
|
let relative_path = get_normalized_relative_path(&file_path, pack_root)?;
|
|
|
|
if let Some(files) = &mut self.files {
|
|
|
|
let removed = files.remove(&relative_path);
|
|
|
|
if let Some(removed) = removed {
|
|
|
|
println!(
|
|
|
|
"Removed file '{relative_path}' -> '{}' from modpack...",
|
|
|
|
removed.target_path
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(self)
|
|
|
|
}
|
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn init_project(&self, directory: &Path) -> Result<()> {
|
2024-08-20 01:20:17 +01:00
|
|
|
let modpack_meta_file_path = directory.join(PathBuf::from(MODPACK_FILENAME));
|
2024-08-14 21:28:40 +01:00
|
|
|
if modpack_meta_file_path.exists() {
|
2024-08-24 13:03:27 +01:00
|
|
|
anyhow::bail!(
|
2024-08-14 22:45:09 +01:00
|
|
|
"{MODPACK_FILENAME} already exists at {}",
|
2024-08-14 21:28:40 +01:00
|
|
|
modpack_meta_file_path.display()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-08-14 22:32:45 +01:00
|
|
|
self.save_to_file(&modpack_meta_file_path)?;
|
|
|
|
println!("MC modpack project initialized at {}", directory.display());
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn save_to_file(&self, path: &PathBuf) -> Result<()> {
|
2024-08-14 21:28:40 +01:00
|
|
|
std::fs::write(
|
2024-08-14 22:32:45 +01:00
|
|
|
path,
|
|
|
|
toml::to_string(self).expect("MC Modpack Meta should be serializable"),
|
2024-08-14 21:28:40 +01:00
|
|
|
)?;
|
2024-08-17 22:34:06 +01:00
|
|
|
// println!("Saved modpack metadata to {}", path.display());
|
2024-08-14 22:32:45 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2024-08-14 21:28:40 +01:00
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn save_current_dir_project(&self) -> Result<()> {
|
2024-08-14 22:32:45 +01:00
|
|
|
let modpack_meta_file_path = std::env::current_dir()?.join(PathBuf::from(MODPACK_FILENAME));
|
|
|
|
self.save_to_file(&modpack_meta_file_path)?;
|
2024-08-14 21:28:40 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2024-08-13 23:56:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl std::default::Default for ModpackMeta {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2024-08-14 21:28:40 +01:00
|
|
|
pack_name: "my_modpack".into(),
|
2024-08-13 23:56:57 +01:00
|
|
|
mc_version: "1.20.1".into(),
|
|
|
|
modloader: ModLoader::Forge,
|
|
|
|
mods: Default::default(),
|
2024-09-01 15:06:21 +01:00
|
|
|
files: Default::default(),
|
2024-08-14 20:51:42 +01:00
|
|
|
default_providers: vec![ModProvider::Modrinth],
|
2024-08-19 00:50:38 +01:00
|
|
|
forbidden_mods: Default::default(),
|
2024-08-13 23:56:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|