Skip to content

Instantly share code, notes, and snippets.

@zaralX
Created March 14, 2025 17:06
Show Gist options
  • Save zaralX/1d46e6052fd4c108914fcb059d0f421b to your computer and use it in GitHub Desktop.
Save zaralX/1d46e6052fd4c108914fcb059d0f421b to your computer and use it in GitHub Desktop.
Minecraft vanilla install and run any version 1.0-latest
use std::collections::HashMap;
use std::{fs, io};
use std::fs::{create_dir_all, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use zip::read::ZipArchive;
const VERSION_MANIFEST_LINK: &str =
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
const ASSETS_LINK: &str =
"https://resources.download.minecraft.net/%A/%B";
#[tokio::main]
async fn main() {
let test = Path::new("./test");
let version = "1.16.5";
let version_type = "release";
let client_type = "vanilla";
let java = "C:\\Program Files\\Java\\jdk1.8.0_333\\bin\\java.exe";
let version_dir = test.join("versions").join(format!("{}-{}-{}", version, version_type, client_type));
create_dir_all(&version_dir).expect("Failed to create dirs");
let response = reqwest::get(VERSION_MANIFEST_LINK)
.await
.expect("Error when get version list")
.text()
.await
.unwrap();
let manifest: serde_json::Value = serde_json::from_str(&response).unwrap();
let version_data: serde_json::Value = get_version_data(version, manifest).await;
let client_jar = version_dir.join("client.jar");
download_file(version_data["downloads"]["client"]["url"].as_str().unwrap(), client_jar.as_path()).await;
let libraries = version_data["libraries"].as_array().unwrap();
let libs_path = test.join("libraries");
let mut libs: Vec<String> = Vec::new();
for library in libraries {
// TODO ADD CHECK RULES
let lib_artifact = &library["downloads"]["artifact"];
let lib_classifier = &library["downloads"]["classifiers"]["natives-windows"];
if !lib_classifier.is_null() {
let lib_path = libs_path.join(&lib_classifier["path"].as_str().unwrap());
download_file(lib_classifier["url"].as_str().unwrap(), lib_path.as_path()).await;
let natives_dir = version_dir.join("natives");
if !natives_dir.exists() {
create_dir_all(&natives_dir).unwrap();
}
extract_jar(lib_path, natives_dir).unwrap();
continue
} else if lib_artifact.is_null() {
println!("NOT FOUNT ARTIFACT OR CLASSIFIER FOR WINDOWS:");
println!("{}", library.to_string());
continue
}
let lib_path = libs_path.join(&lib_artifact["path"].as_str().unwrap());
download_file(lib_artifact["url"].as_str().unwrap(), lib_path.as_path()).await;
libs.push(get_absolute_path(lib_path));
}
let assets_data = &version_data["assetIndex"];
let assets_id = &assets_data["id"];
let assets_file_name = assets_id.as_str().unwrap().to_owned() + ".json";
let assets_path = test.join("assets");
let assets_file_path = &assets_path.join("indexes").join(assets_file_name);
let mut assets_list: serde_json::Value;
if !&assets_file_path.exists() {
let response = reqwest::get(assets_data["url"].as_str().unwrap())
.await
.expect("Error when get assets")
.text()
.await
.unwrap();
assets_list = serde_json::from_str(&response).unwrap();
if !&assets_file_path.parent().unwrap().exists() {
create_dir_all(&assets_file_path.parent().unwrap()).unwrap();
}
let mut assets_file = File::create(assets_file_path).unwrap();
assets_file.write_all(assets_list.to_string().as_ref()).expect("Failed to save assets list");
} else {
let mut file = File::open(assets_file_path).unwrap();
let mut json_string = String::new();
file.read_to_string(&mut json_string).unwrap();
assets_list = serde_json::from_str(&json_string).unwrap();
}
let assets_objects_path = assets_path.join("objects");
let assets_objects = assets_list["objects"].as_object().unwrap();
let mut asset_counter = 1;
for (key, value) in assets_objects {
println!("Asset {}/{}", asset_counter, assets_objects.iter().count().to_string());
asset_counter += 1;
let hash = value["hash"].as_str().unwrap();
let folder = &hash[..2];
let object_folder = assets_objects_path.join(folder);
if !&object_folder.exists() {
create_dir_all(&object_folder).unwrap();
}
let object_link = ASSETS_LINK.replace("%A", folder).replace("%B", hash);
let object_file_path = object_folder.join(hash);
download_file(object_link.as_str(), object_file_path.as_path()).await;
}
let required_java_version = version_data["javaVersion"]["majorVersion"].as_i64().unwrap();
let required_java_type = version_data["javaVersion"]["component"].as_str().unwrap();
// let arguments_game_data = version_data["arguments"]["game"].as_array().unwrap();
// let arguments_jvm_data = version_data["arguments"]["jvm"].as_array().unwrap();
let arg_class_path: String = libs.join(";") + ";" + &*get_absolute_path(client_jar);
let mut arguments: Vec<String> = Vec::new();
if required_java_type.eq("jre-legacy") {
let natives_dir = version_dir.join("natives");
create_dir_all(&natives_dir).unwrap();
arguments.push(format!("-Djava.library.path={}", get_absolute_path(natives_dir)).to_string());
}
arguments.push("-cp".to_string());
arguments.push(arg_class_path);
arguments.push(version_data["mainClass"].as_str().unwrap().to_string());
arguments.push("--version".to_string());
arguments.push(version.to_string());
arguments.push("--accessToken".to_string());
arguments.push("null".to_string());
arguments.push("--assetsDir".to_string());
arguments.push(get_absolute_path(assets_path));
arguments.push("--assetIndex".to_string());
arguments.push((&assets_data["id"]).as_str().unwrap().to_string());
arguments.push("--gameDir".to_string());
arguments.push(get_absolute_path(version_dir.clone()));
println!("{}", arguments.join(" ").as_str());
let mut command = Command::new(java);
command.args(arguments);
command.current_dir(&version_dir);
command.spawn().expect("Ошибка при запуске Minecraft");
loop {}
}
pub async fn get_version_data(version: &str, manifest: serde_json::Value) -> serde_json::Value {
let version_url = manifest["versions"]
.as_array()
.unwrap()
.iter()
.find(|v| v["id"].as_str().unwrap() == version)
.unwrap()["url"]
.as_str()
.unwrap();
let version_json = reqwest::get(version_url).await.unwrap().text().await.unwrap();
serde_json::from_str(&version_json).unwrap()
}
pub async fn download_file(url: &str, dir: &Path) {
if !&dir.parent().unwrap().exists() {
create_dir_all(&dir.parent().unwrap()).unwrap();
}
if !&dir.exists() {
println!("Downloading: {}", &dir.to_string_lossy());
let jar_data = reqwest::get(url).await.unwrap().bytes().await.unwrap();
fs::write(&dir, jar_data).unwrap();
} else {
println!("{} exists! Skipped download.", &dir.to_string_lossy());
}
}
pub fn get_absolute_path(path: PathBuf) -> String {
let full_path = path.canonicalize().unwrap();
let full_path_str = full_path.to_str().unwrap();
full_path_str.strip_prefix(r"\\?\").unwrap_or(full_path_str).to_owned()
}
fn extract_jar(jar_path: PathBuf, output_dir: PathBuf) -> io::Result<()> {
let file = File::open(jar_path)?;
let mut archive = ZipArchive::new(file)?;
create_dir_all(&output_dir)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let outpath = output_dir.join(file.name());
if file.name().ends_with('/') {
create_dir_all(&outpath)?;
} else {
if let Some(parent) = outpath.parent() {
create_dir_all(parent)?;
}
let mut outfile = File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
}
}
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment