use std::marker::PhantomData;
use anyhow::{Ok, Result};
use sha1::Sha1;
use tokio::{fs, io::AsyncReadExt, sync::mpsc};
use crate::{utils::{check_hash, check_rules, download_all, download_txt, get_os, BetterPath, DownloadAllMessage}, LauncherContext};
use super::{schemas::{AssetsIndex, Library, Resource, VersionJSON}, version::{DMCLCExtraData, MinecraftInstallation}};
pub use super::schemas::{VersionList, VersionInfo};
const MC_MANIFEST_URL: &str = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
impl VersionList {
pub async fn get_list() -> Result<VersionList> {
Ok(reqwest::get(MC_MANIFEST_URL)
.await?
.json()
.await?)
}
pub fn find_by_id(&self, id: &str) -> Option<&VersionInfo> {
self.versions.iter().find(|i|i.id == id)
}
pub fn get_latest_release(&self) -> Option<&VersionInfo> {
self.find_by_id(&self.latest.release)
}
pub fn get_latest_snapshot(&self) -> Option<&VersionInfo> {
self.find_by_id(&self.latest.release)
}
}
impl VersionInfo {
pub async fn install<'l>(&self, launcher: &'l LauncherContext, name: &str, channel: mpsc::UnboundedSender<DownloadAllMessage>) -> Result<MinecraftInstallation<'l>> {
let res = reqwest::get(&self.url).await?;
let text = res.text().await?;
let obj: VersionJSON = serde_json::from_str(&text)?;
let version_dir = *(&launcher.root_path / "versions" / name);
fs::create_dir_all(version_dir.clone()).await?;
fs::write(&version_dir / format!("{name}.json"), text).await?;
let v = MinecraftInstallation::<'l>::new(launcher, obj, name, Some(DMCLCExtraData {
version: Some(self.id.clone()),
components: vec![],
independent_game_dir: true,
before_command: None,
with_java: None,
extra_game_arguments: None,
extra_jvm_arguments: None
}));
v.complete_files(true, true, channel).await?;
Ok(v)
}
}
impl <'l> MinecraftInstallation<'l> {
pub async fn complete_files(&self, always_download_nohash: bool, fix_client_jar: bool, channel: mpsc::UnboundedSender<DownloadAllMessage>) -> Result<()> {
let mut resources: Vec<(Resource, BetterPath)> = Vec::new();
let client_res = &self.obj.get_base().downloads.client;
let version_dir = *(&self.launcher.root_path / "versions" / &self.name);
if fix_client_jar { resources.push((client_res.clone(), *(&version_dir / format!("{}.jar", self.name)))); }
resources.extend(self.install_resources().await?);
resources.extend(self.install_libraries(&self.obj.get_base().libraries, always_download_nohash)?);
download_all(
&resources, channel,
self.launcher.download_threads_per_file, self.launcher.download_parallel_files,
self.launcher.download_retries,self.launcher.bmclapi_mirror.clone()
).await?;
Ok(())
}
async fn install_resources(&self) -> Result<Vec<(Resource, BetterPath)>> {
let mut res = vec![];
let assets = &self.obj.get_base().asset_index;
let asset_path = &(&self.launcher.root_path / "assets/indexes" / &format!("{}.json", assets.res.id));
let index = if !check_hash(asset_path, &assets.res.res.sha1, assets.res.res.size, PhantomData::<Sha1>).await {
download_txt(&assets.res.res.url, asset_path).await?
} else {
let mut str = String::new();
tokio::fs::File::open(asset_path).await?.read_to_string(&mut str).await?;
str
};
let index: AssetsIndex = serde_json::from_str(&index)?;
for (_, val) in index.objects.iter() {
let first_two = val.hash.get(0..=1).unwrap(); let path = format!("{first_two}/{}", val.hash);
res.push((Resource {
url: format!("https://resources.download.minecraft.net/{path}"),
sha1: val.hash.clone(),
size: val.size
}, *(&self.launcher.root_path / "assets/objects" / &path)))
}
Ok(res)
}
pub(crate) fn install_libraries(&self, libraries: &Vec<Library>, always_download_nohash: bool) -> Result<Vec<(Resource, BetterPath)>> {
let mut res = vec![];
let lib_path = &*(&self.launcher.root_path / "libraries");
for lib in libraries {
if !check_rules(&lib.get_base().rules) {
continue;
}
match lib {
Library::FabricWithHash(l) => {
res.push((Resource {
url: format!("{}/{}", l.url, l.base.name.to_path()),
sha1: l.sha1.clone(),
size: l.size
}, *(lib_path / l.base.name.to_path())));
},
Library::FabricOldForgeAndLiteLoader(l) => {
if !l.clientreq {
continue;
}
res.push((Resource {
url: format!("{}/{}", l.url, l.base.name.to_path()),
sha1: always_download_nohash.to_string(),
size: 0
}, *(lib_path / l.base.name.to_path())));
}
Library::VanillaForgeAndNeo(l) => {
res.push((l.downloads.artifact.res.clone(), *(lib_path / &l.downloads.artifact.path)))
}
Library::VanillaNatives(l) => {
if let Some(os) = l.natives.get(&get_os()) {
let artifact = l.downloads.classifiers.get(os).unwrap();
res.push((artifact.res.clone(), *(lib_path / &artifact.path)))
}
}
Library::BaseOnly(l) => {
res.push((Resource {
url: format!("https://libraries.minecraft.net/{}", l.name.to_path()),
sha1: always_download_nohash.to_string(),
size: 0
}, *(lib_path / l.name.to_path())));
}
}
}
Ok(res)
}
}