use std::{
collections::HashMap,
io::Read,
path::{Path, PathBuf},
};
use anyhow::Context;
use image::DynamicImage;
use crate::prelude::*;
#[derive(Debug, Clone, Default)]
pub struct Mod {
file_name: String,
path: PathBuf,
enabled: bool,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum FabricModIcon {
Multiply(HashMap<String, String>),
Single(String),
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct FabricModMeta {
pub name: String,
pub description: String,
pub version: String,
pub icon: Option<FabricModIcon>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct ForgeModMeta {
pub name: String,
pub description: String,
pub version: String,
#[serde(rename = "logoFile")]
pub logo_file: String,
}
#[derive(Debug, Clone)]
pub enum ModMeta {
Fabric(FabricModMeta),
Forge(ForgeModMeta),
}
impl ModMeta {
pub fn name(&self) -> &str {
match &self {
ModMeta::Fabric(m) => m.name.as_str(),
ModMeta::Forge(m) => m.name.as_str(),
}
}
pub fn version(&self) -> &str {
match &self {
ModMeta::Fabric(m) => m.version.as_str(),
ModMeta::Forge(m) => m.version.as_str(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct NewForgeModMeta {
pub mods: Vec<ForgeModMeta>,
}
impl Mod {
pub async fn from_path(path: impl AsRef<Path>) -> DynResult<Self> {
let path = path.as_ref();
let file_name = path.file_name().unwrap().to_string_lossy().to_string();
let enabled = !file_name.ends_with(".disabled");
Ok(Self {
file_name,
path: path.to_path_buf(),
enabled,
})
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn file_name(&self) -> &str {
&self.file_name
}
pub async fn display_name(&self) -> String {
self.try_get_mod_name().await.unwrap_or_else(|_| {
self.file_name()
.trim_end_matches(".jar.disabled")
.trim_end_matches(".jar")
.to_owned()
})
}
pub fn path(&self) -> &PathBuf {
&self.path
}
pub async fn try_get_mod_meta(&self) -> DynResult<ModMeta> {
let path = self.path.to_owned();
inner_future::unblock(move || -> DynResult<ModMeta> {
let mut z = zip::ZipArchive::new(
std::fs::OpenOptions::new()
.read(true)
.open(&path)
.with_context(|| format!("无法打开模组文件 {}", path.to_string_lossy()))
.with_context(|| format!("无法读取模组文件 {}", path.to_string_lossy()))?,
)?;
if let Ok(meta) = z.by_name("fabric.mod.json").and_then(|r| {
serde_json::from_reader::<_, FabricModMeta>(r)
.map_err(|_| zip::result::ZipError::InvalidArchive("fabric.mod.json"))
}) {
Ok(ModMeta::Fabric(meta))
} else if let Ok(meta) = z.by_name("mods.toml").and_then(|mut r| {
let mut buf = String::with_capacity(r.size() as _);
r.read_to_string(&mut buf).unwrap_or_default();
toml::from_str::<NewForgeModMeta>(&buf)
.map_err(|_| zip::result::ZipError::InvalidArchive("mods.toml"))
}) {
Ok(ModMeta::Forge(meta.mods.first().cloned().ok_or_else(
|| anyhow::anyhow!("Can't get mod info from mcmod.info"),
)?))
} else if let Ok(meta) = z.by_name("mcmod.info").and_then(|r| {
serde_json::from_reader::<_, Vec<ForgeModMeta>>(r)
.map_err(|_| zip::result::ZipError::InvalidArchive("mcmod.info"))
}) {
Ok(ModMeta::Forge(meta.first().cloned().ok_or_else(|| {
anyhow::anyhow!("Can't get mod info from mcmod.info")
})?))
} else {
anyhow::bail!("Mod name not found")
}
})
.await
}
pub async fn try_get_mod_name(&self) -> DynResult<String> {
Ok(self.try_get_mod_meta().await?.name().to_owned())
}
pub async fn try_get_mod_icon(&self) -> DynResult<DynamicImage> {
let icon_path = match self.try_get_mod_meta().await? {
ModMeta::Fabric(meta) => match meta.icon {
Some(FabricModIcon::Multiply(icon)) => {
icon.iter().next().map(|x| x.0.to_owned()).ok_or_else(|| {
anyhow::anyhow!("Can't find icon from such multiply fabric mod icons")
})?
}
Some(FabricModIcon::Single(icon)) => icon,
None => anyhow::bail!("Can't find icon from such multiply fabric mod icons"),
},
ModMeta::Forge(meta) => meta.logo_file,
};
let path = self.path.to_owned();
inner_future::unblock(move || -> DynResult<DynamicImage> {
let mut z = zip::ZipArchive::new(std::fs::OpenOptions::new().read(true).open(path)?)?;
let mut r = z.by_name(icon_path.trim_start_matches(['/', '\\']))?;
let mut buf = Vec::with_capacity(r.size() as _);
r.read_to_end(&mut buf)?;
Ok(image::load_from_memory(&buf)?)
})
.await
}
pub async fn enable(&mut self) -> DynResult {
if self.file_name.ends_with(".disabled") && !self.enabled {
let mut path = self.path.clone();
path.set_file_name(&self.file_name[..self.file_name.len() - 9]);
inner_future::fs::rename(&self.path, &path).await?;
self.file_name = path.file_name().unwrap().to_string_lossy().to_string();
self.path = path;
self.enabled = true;
}
Ok(())
}
pub async fn disable(&mut self) -> DynResult {
if !self.file_name.ends_with(".disabled") && self.enabled {
let mut path = self.path.clone();
path.set_file_name(&self.file_name);
path.set_extension(format!(
"{}.disabled",
path.extension()
.map(|x| x.to_string_lossy())
.unwrap_or_default()
));
inner_future::fs::rename(&self.path, &path).await?;
self.file_name = path.file_name().unwrap().to_string_lossy().to_string();
self.path = path;
self.enabled = false;
}
Ok(())
}
pub async fn remove(self) -> DynResult {
inner_future::fs::remove_file(self.path).await?;
Ok(())
}
}