Created
March 14, 2025 17:06
-
-
Save zaralX/1d46e6052fd4c108914fcb059d0f421b to your computer and use it in GitHub Desktop.
Minecraft vanilla install and run any version 1.0-latest
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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