use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::{env, fs};
use chrono::{DateTime, TimeZone, Utc};
use rand::Rng;
use uuid::Uuid;
use crate::types::shared_types::{ClientJson, VersionListManifestJson};
use crate::types::{
Articles, LatestMinecraftVersions, MinecraftNewsOptions, MinecraftOptions, MinecraftVersionInfo,
};
use self::helper::get_requests_response_cache;
pub mod helper;
pub mod java;
pub mod natives;
pub fn get_minecraft_directory() -> PathBuf {
let os = env::consts::OS;
if os == "windows" {
let home = env::var("APPDATA").unwrap_or_else(|_| {
let home = env::var("USERPROFILE").expect("USERPROFILE is not set");
home
});
let appdata = format!(r"{}\.minecraft", home);
PathBuf::from(appdata)
} else if os == "macos" {
let home = env::var("HOME").expect("HOME is not set");
PathBuf::from(format!("{}/Library/Application Support/minecraft", home))
} else {
let home = env::var("HOME").expect("HOME is not set");
PathBuf::from(format!("{}/.minecraft", home))
}
}
pub fn get_latest_version() -> Result<LatestMinecraftVersions, Box<dyn std::error::Error>> {
let response = get_requests_response_cache(
"https://launchermeta.mojang.com/mc/game/version_manifest_v2.json",
)?;
let res: VersionListManifestJson = serde_json::from_str(&response)?;
let latest = res.latest;
let release = latest.get("release").unwrap();
let snapshot = latest.get("snapshot").unwrap();
Ok(LatestMinecraftVersions {
release: release.to_string(),
snapshot: snapshot.to_string(),
})
}
pub fn get_version_list() -> Result<Vec<MinecraftVersionInfo>, Box<dyn std::error::Error>> {
let response = get_requests_response_cache(
"https://launchermeta.mojang.com/mc/game/version_manifest_v2.json",
)?;
let vlist: VersionListManifestJson = serde_json::from_str(&response)?;
let mut res = Vec::new();
for v in vlist.versions {
res.push(MinecraftVersionInfo {
id: v.id,
r#type: v.r#type,
release_time: DateTime::parse_from_rfc3339(v.release_time.as_str())?.into(),
compliance_level: v.compliance_level,
})
}
Ok(res)
}
pub fn get_installed_versions(
minecraft_directory: impl AsRef<Path>,
) -> Result<Vec<MinecraftVersionInfo>, Box<dyn std::error::Error>> {
let versions_path = minecraft_directory.as_ref().join("versions");
let dir_list = match fs::read_dir(versions_path) {
Ok(dir) => dir,
Err(_) => return Ok(vec![]),
};
let mut version_list = Vec::new();
for entry in dir_list {
let entry = entry?;
let mut dir_name = entry.file_name().to_string_lossy().to_string();
dir_name.push_str(".json");
let path = entry.path().join(dir_name);
if !path.is_file() || path.extension().unwrap_or_default() != "json" {
continue;
}
let file_content = fs::read_to_string(&path)?;
let version_data: ClientJson = serde_json::from_str(file_content.as_str())?;
let release_time = match DateTime::parse_from_rfc3339(
&version_data.release_time.unwrap_or("0".to_string()),
) {
Ok(time) => time.with_timezone(&Utc),
Err(_) => Utc.timestamp_opt(0, 0).unwrap(),
};
let info = MinecraftVersionInfo {
id: version_data.id.unwrap_or_default(),
r#type: version_data.r#type.unwrap_or_default(),
release_time,
compliance_level: version_data.compliance_level.unwrap_or_default(),
};
version_list.push(info);
}
Ok(version_list)
}
pub fn get_available_versions<P: AsRef<Path>>(minecraft_directory: P) -> Vec<MinecraftVersionInfo> {
let mut version_list = Vec::new();
let mut version_check = HashSet::new();
if let Ok(vlist) = get_version_list() {
for i in vlist {
version_list.push(i.clone());
version_check.insert(i.id);
}
}
if let Ok(installed_versions) = get_installed_versions(minecraft_directory) {
for i in installed_versions {
if !version_check.contains(&i.id) {
version_list.push(i);
}
}
}
version_list
}
pub fn get_java_executable() -> Option<String> {
let os = std::env::consts::OS;
if os == "windows" {
if let Some(java_home) = env::var_os("JAVA_HOME") {
let java_home_path = Path::new(&java_home);
let javaw_path = java_home_path.join("bin").join("javaw.exe");
if javaw_path.is_file() {
return Some(javaw_path.to_string_lossy().into_owned());
}
}
if let Ok(_) =
fs::metadata(r"C:\Program Files (x86)\Common Files\Oracle\Java\javapath\javaw.exe")
{
return Some(
r"C:\Program Files (x86)\Common Files\Oracle\Java\javapath\javaw.exe".to_string(),
);
}
if let Ok(javaw_path) = which::which("javaw") {
return Some(javaw_path.to_string_lossy().into_owned());
}
return Some("javaw".to_string());
} else if let Some(java_home) = env::var_os("JAVA_HOME") {
let java_home_path = Path::new(&java_home);
let java_path = java_home_path.join("bin").join("java");
if java_path.is_file() {
return Some(java_path.to_string_lossy().into_owned());
}
} else if os == "macos" || os == "ios" {
if let Ok(java_path) = which::which("java") {
return Some(java_path.to_string_lossy().into_owned());
}
} else {
if let Ok(java_link) = fs::read_link("/etc/alternatives/java") {
return Some(java_link.to_string_lossy().into_owned());
} else if let Ok(runtime_link) = fs::read_link("/usr/lib/jvm/default-runtime") {
let java_path = Path::new("/usr/lib/jvm")
.join(runtime_link)
.join("bin")
.join("java");
if java_path.is_file() {
return Some(java_path.to_string_lossy().into_owned());
}
} else if let Ok(java_path) = which::which("java") {
return Some(java_path.to_string_lossy().into_owned());
}
}
Some("java".to_string())
}
pub fn get_core_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
pub fn generate_test_options() -> MinecraftOptions {
let username = format!("Player{}", rand::thread_rng().gen_range(100..1000));
let uuid = Uuid::new_v4();
let token = String::new();
MinecraftOptions::new(username, uuid.to_string(), token)
}
pub fn is_version_valid(version: &str, minecraft_directory: impl AsRef<Path>) -> bool {
let versions_path = minecraft_directory.as_ref().join("versions");
if versions_path.join(version).is_dir() {
return true;
}
if let Ok(version_list) = get_version_list() {
return version_list.iter().any(|i| i.id == version);
}
false
}
pub fn get_minecraft_news(options: MinecraftNewsOptions) -> Result<Articles, reqwest::Error> {
let client = reqwest::blocking::Client::new();
let url = format!(
"https://www.minecraft.net/content/minecraft-net/_jcr_content.articles.grid?pageSize={}",
options.page_size
);
let user_agent = format!("mc-launcher-core/{}", get_core_version());
let response: Articles = client
.get(&url)
.header(reqwest::header::USER_AGENT, user_agent)
.send()?
.json()?;
Ok(response)
}
pub fn is_vanilla_version(version: &str) -> bool {
if let Ok(version_list) = get_version_list() {
for i in version_list {
if i.id == version {
return true;
}
}
}
false
}
pub fn is_platform_supported() -> bool {
match env::consts::OS {
"windows" | "macos" | "linux" => true,
_ => false,
}
}
pub fn is_minecraft_installed(minecraft_directory: impl AsRef<Path>) -> bool {
let versions_dir = minecraft_directory.as_ref().join("versions");
let libraries_dir = minecraft_directory.as_ref().join("libraries");
let assets_dir = minecraft_directory.as_ref().join("assets");
versions_dir.is_dir() && libraries_dir.is_dir() && assets_dir.is_dir()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_get_minecraft_directory() {
let minecraft_directory = get_minecraft_directory();
println!("Minecraft directory: {:?}", minecraft_directory);
}
#[test]
fn debug_get_latest_version() {
if let Ok(latest_version) = get_latest_version() {
println!("Minecraft latest_version: {:#?}", latest_version);
}
}
#[test]
fn debug_get_version_list() {
if let Ok(version_list) = get_version_list() {
println!("Minecraft version_list: {:#?}", version_list);
}
}
#[test]
fn debug_get_installed_versions() {
}
#[test]
fn debug_get_available_versions() {
}
#[test]
fn debug_get_java_executable() {
println!("Java excutable path: {:#?}", get_java_executable());
}
#[test]
fn debug_get_core_version() {
println!("mc-launcher-core version: {}", get_core_version());
}
#[test]
fn debug_generate_test_options() {
println!("Random MinecraftOptions: {:#?}", generate_test_options());
}
#[test]
fn debug_is_version_valid() {
}
#[test]
fn debug_get_minecraft_news() {
}
#[test]
fn test_is_vanilla_version() {
assert_eq!(is_vanilla_version("1.20"), true);
assert_eq!(is_vanilla_version("20.24"), false);
}
#[test]
fn debug_is_minecraft_installed() {
}
}