use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use indexmap::IndexMap;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use std::io::Write;
const BASE: &str = "https://resources.download.minecraft.net/";
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct ObjectData {
pub hash: String,
pub size: usize,
}
impl ObjectData {
pub fn get_link(&self) -> String {
format!("{BASE}{}/{}", &self.hash[..2], &self.hash)
}
pub fn get_path(&self) -> PathBuf {
PathBuf::from(&self.hash[..2]).join(&self.hash)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct DownloadData {
pub sha1: String,
pub size: usize,
pub url: String,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Resources {
pub objects: IndexMap<String, ObjectData>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct MinecraftVersion {
pub id: String,
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub instance_type: String,
pub url: String,
pub time: String,
#[cfg_attr(feature = "serde", serde(rename = "releaseTime"))]
pub release_time: String,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct MinecraftVersions {
pub latest: Latest,
pub versions: Box<[MinecraftVersion]>,
}
impl MinecraftVersions {
pub fn get_latest_release_id(&self) -> &str {
&self.latest.release
}
pub fn get_latest_snapshot_id(&self) -> &str {
&self.latest.snapshot
}
pub fn get_latest_release_url(&self) -> &str {
&self
.versions
.iter()
.find(|v| v.id == self.get_latest_release_id())
.unwrap()
.url
}
pub fn get_latest_snapshot_url(&self) -> &str {
&self
.versions
.iter()
.find(|v| v.id == self.get_latest_snapshot_id())
.unwrap()
.url
}
pub fn get_instance_url(&self, instance: &str) -> Option<&str> {
for version in &self.versions {
if version.id == instance {
return Some(&version.url);
}
}
None
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Latest {
pub release: String,
pub snapshot: String,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Library {
pub downloads: Option<LibraryDownloads>,
pub name: String,
pub rules: Option<Box<[Rule]>>,
}
impl Library {
pub fn get_os(&self) -> Option<Os> {
self.rules
.as_ref()
.and_then(|r| r.iter().find(|x| x.os.is_some()).unwrap().os)
}
pub fn get_url(&self) -> &str {
self.downloads.as_ref().unwrap().artifact.url.as_str()
}
pub fn get_rel_path(&self) -> Option<&Path> {
self.downloads.as_ref().map(|ld| ld.artifact.path.as_path())
}
pub fn get_hash(&self) -> Option<&str> {
self.downloads.as_ref().map(|ld| ld.artifact.sha1.as_str())
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct AssetIndex {
pub id: String,
pub sha1: String,
pub size: usize,
#[cfg_attr(feature = "serde", serde(rename = "totalSize"))]
pub total_size: u128,
pub url: String,
}
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone)]
pub struct Profile {
#[cfg_attr(feature = "serde", serde(default = "Default::default"))]
pub icon: String,
pub last_version_id: String,
pub name: String,
pub game_dir: Option<PathBuf>,
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub profile_type: String,
#[cfg_attr(feature = "serde", serde(default = "Default::default"))]
pub java_args: String,
}
impl Profile {
pub fn new(
icon: &str,
last_version_id: &str,
name: &str,
profile_type: &str,
path: Option<&Path>,
) -> Self {
let path = path.map(std::borrow::ToOwned::to_owned);
Self {
icon: icon.to_string(),
last_version_id: last_version_id.to_string(),
name: name.to_string(),
game_dir: path,
profile_type: profile_type.to_string(),
java_args: "".to_string(),
}
}
#[cfg(feature = "serde")]
pub fn get_id(&self) -> Option<String> {
let minecraft_path = self
.game_dir
.as_ref()
.map(|x| {
x.ancestors()
.find(|e| e.file_name().is_some_and(|f| f == ".minecraft"))
})??
.to_path_buf();
let version_path = minecraft_path
.join("versions")
.join(&self.last_version_id)
.join(&self.last_version_id)
.with_extension("json");
let file = std::fs::File::open(version_path).ok()?;
let r: Root = serde_json::from_reader(file).ok()?;
Some(r.inherits_from.unwrap_or(r.id.clone()))
}
}
#[cfg_attr(
feature = "serde",
derive(Deserialize, Serialize),
serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub crash_assistance: bool,
pub enable_advanced: bool,
pub enable_analytics: bool,
pub enable_historical: bool,
pub enable_releases: bool,
pub enable_snapshots: bool,
pub keep_launcher_open: bool,
pub profile_sorting: String,
pub show_game_log: bool,
pub show_menu: bool,
pub sound_on: bool,
}
impl Default for Settings {
fn default() -> Self {
Self {
crash_assistance: false,
enable_advanced: false,
enable_analytics: false,
enable_historical: true,
enable_releases: true,
enable_snapshots: false,
keep_launcher_open: true,
profile_sorting: "ByLastPlayed".to_owned(),
show_game_log: true,
show_menu: true,
sound_on: true,
}
}
}
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, Default, Clone)]
pub struct ProfilesJson {
pub profiles: HashMap<String, Profile>,
pub settings: Settings,
pub version: usize,
}
impl ProfilesJson {
#[cfg(feature="serde")]
pub fn read_json_from<I: AsRef<Path>>(path: I) -> Result<ProfilesJson, std::io::Error> {
let content = std::io::read_to_string(std::fs::File::open(path)?)?;
let parsed =
serde_json::from_str::<ProfilesJson>(&content).map_err(std::io::Error::from)?;
Ok(parsed)
}
pub fn insert(&mut self, profile_name: &str, data: Profile) {
self.get_mut_profiles()
.insert(profile_name.to_owned(), data);
}
pub fn remove(&mut self, name: &str) {
self.profiles.remove(name);
}
fn get_mut_profiles(&mut self) -> &mut HashMap<String, Profile> {
&mut self.profiles
}
pub fn get_profiles(&self) -> &HashMap<String, Profile> {
&self.profiles
}
#[cfg(feature="serde")]
pub fn save(&self) -> std::io::Result<()> {
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(
get_minecraft_path()
.ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
".minecraft not found",
))?
.join("launcher_profiles.json"),
)?;
file.write_all(serde_json::to_string_pretty(&self)?.as_bytes())?;
Ok(())
}
}
#[cfg_attr(feature="serde", derive(Serialize, Deserialize),serde(rename_all = "camelCase") )]
#[derive( Debug)]
pub struct Root {
pub arguments: Arguments,
pub asset_index: AssetIndex,
#[cfg_attr(feature="serde", serde(default = "Default::default"))]
pub assets: String,
pub downloads: HashMap<String, DownloadData>,
pub id: String,
pub java_version: JavaVersion,
pub libraries: Box<[Library]>,
pub inherits_from: Option<String>,
#[cfg_attr(feature="serde", serde(default = "Default::default"))]
pub main_class: String,
#[cfg_attr(feature="serde", serde(rename = "type"))]
pub version_type: String,
}
impl Root {
pub fn get_index_name(&self) -> String {
self.assets.clone() + ".json"
}
}
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct JavaVersion {
pub component: String,
#[cfg_attr(feature="serde", serde(rename = "majorVersion"))]
pub major_version: usize,
}
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Arguments {
pub game: Box<[GameArgument]>,
}
#[cfg_attr(feature="serde", derive(Serialize, Deserialize), serde(untagged))]
#[derive(Debug)]
pub enum GameArgument {
String(String),
Object {
rules: Box<[Rule]>,
value: ValueType,
},
}
#[cfg_attr(feature="serde", derive(Serialize, Deserialize), serde(untagged))]
#[derive(Debug)]
pub enum ValueType {
Single(String),
Multiple(Box<[String]>),
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Rule {
pub action: String,
pub os: Option<Os>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "name"))]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Os {
#[cfg_attr(feature = "serde", serde(rename = "linux"))]
Linux,
#[cfg_attr(feature = "serde", serde(rename = "windows"))]
Windows,
#[cfg_attr(feature = "serde", serde(other))]
Other,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct LibraryDownloads {
pub artifact: Artifact,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Artifact {
pub path: PathBuf,
pub sha1: String,
pub size: u64,
pub url: String,
}
pub fn get_minecraft_path() -> Option<PathBuf> {
if cfg!(target_os = "windows") {
if let Ok(appdata) = std::env::var("APPDATA") {
let minecraft_path = PathBuf::from(appdata).join(".minecraft");
Some(minecraft_path)
} else {
None
}
} else if cfg!(target_os = "linux") {
if let Some(home_dir) = dirs::home_dir() {
let minecraft_path = home_dir.join(".minecraft");
Some(minecraft_path)
} else {
None
}
} else {
None
}
}
pub const RUNTIMES_URL: &str = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json";
pub type RuntimeName = String;
pub type OsName = String;
pub type Runtime = HashMap<RuntimeName, Box<[RuntimeData]>>;
pub type FileRelPath = PathBuf;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Runtimes {
pub linux: Runtime,
#[cfg_attr(feature = "serde", serde(rename = "windows-x64"))]
pub windowsx64: Runtime,
#[cfg_attr(feature = "serde", serde(rename = "mac-os"))]
pub macos: Runtime,
#[cfg_attr(feature = "serde", serde(rename = "mac-os-arm64"))]
pub macosarm: Runtime,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct RuntimeData {
manifest: Manifest,
}
impl RuntimeData {
pub fn get_url(&self) -> &str {
&self.manifest.url
}
pub fn get_size(&self) -> usize {
self.manifest.size
}
pub fn get_sha1(&self) -> &str {
&self.manifest.sha1
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct Manifest {
pub sha1: String,
pub size: usize,
pub url: String,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct RuntimeFiles {
pub files: HashMap<FileRelPath, RuntimeFile>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug)]
pub struct RuntimeFile {
#[cfg_attr(feature = "serde", serde(default))]
pub downloads: HashMap<String, Manifest>,
#[cfg_attr(feature = "serde", serde(default))]
pub executable: bool,
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub file_type: String,
}