首发于Enaium的个人博客
好了,我们已经完成了所有的准备工作,现在我们可以开始编写启动游戏的代码了。
首先我们需要添加几个依赖。
model = { path = "../model" }
parse = { path = "../parse" }
download = { path = "../download" }
clap = { version = "4.5" }
zip = "2.1"
clap
用于解析命令行参数,zip
用于解压文件。
首先创建一个cli
函数用于构建我们的命令行。
fn cli() -> Command {Command::new("rmcl").about("A Minecraft launcher written in Rust").version("0.1.0").author("Enaium").subcommand_required(true).arg_required_else_help(true).allow_external_subcommands(true).subcommand(Command::new("search").about("Search Game").arg(arg!([VERSION] "Game version")).arg(arg!(-t --type <TYPE> "Game type").value_parser(["release", "snapshot", "old_beta", "old_alpha"]).require_equals(true).default_value("release").default_missing_value("release"),),).subcommand(Command::new("download").about("Download Game").arg(arg!(<VERSION> "Game Version")).arg_required_else_help(true),).subcommand(Command::new("launch").about("Launch Game").arg(arg!(<VERSION> "Game Version")).arg_required_else_help(true),)
}
接着创建一个get_version_manifest
函数用于获取游戏的所有版本清单。
fn get_version_manifest() -> model::version_manifest::VersionManifest {return get("https://launchermeta.mojang.com/mc/game/version_manifest.json").unwrap().json::<version_manifest::VersionManifest>().unwrap();
}
之后编写每个子命令的处理函数。
fn search(sub_matches: &clap::ArgMatches) {let version = sub_matches.get_one::<String>("VERSION");let type_ = sub_matches.get_one::<String>("type").unwrap();let versions = get_version_manifest().versions;let versions = versions.iter().filter(|v| {(if version.is_some() {v.id.contains(version.unwrap())} else {true}) && v.type_.eq(type_)});for version in versions {println!("Version:{}", version.id);}
}
fn download(sub_matches: &clap::ArgMatches) {let game_dir = std::env::current_dir().unwrap().join(".minecraft");let version = sub_matches.get_one::<String>("VERSION").unwrap();let versions = get_version_manifest().versions;if let Some(version) = versions.iter().find(|v| v.id.eq(version)) {version.download(&game_dir).unwrap_or_else(|err| {panic!("Version download error:{:?}", err);});} else {println!("Version:{} not found", version);}
}
fn launch(sub_matches: &clap::ArgMatches) {let game_dir = std::env::current_dir().unwrap().join(".minecraft");let libraries_dir = game_dir.join("libraries");let assets_dir = game_dir.join("assets");let version = sub_matches.get_one::<String>("VERSION").unwrap();let version_dir = game_dir.join("versions").join(version);let natives_dir = version_dir.join("natives");let config_path = version_dir.join(format!("{}.json", version));let version_path = version_dir.join(format!("{}.jar", version));if !version_path.exists() || !config_path.exists() {println!("Version:{} not found", version_path.display());return;}let version = &Version::parse(&std::fs::read_to_string(&config_path).unwrap()).unwrap();for library in &version.libraries {if library.allowed() && library.name.contains("natives") {extract_jar(&libraries_dir.join(&library.downloads.artifact.path),&natives_dir,);}}let classpath = format!("{}{}",&version.libraries.iter().map(|library| {format!("{}{}",libraries_dir.join(&library.downloads.artifact.path).display(),if cfg!(windows) { ";" } else { ":" })}).collect::<String>(),version_path.display());std::process::Command::new("java").current_dir(&game_dir).arg(format!("-Djava.library.path={}", natives_dir.display())).arg("-Dminecraft.launcher.brand=rmcl").arg("-cp").arg(classpath).arg(&version.main_class).arg("--username").arg("Enaium").arg("--version").arg(&version.id).arg("--gameDir").arg(game_dir).arg("--assetsDir").arg(assets_dir).arg("--assetIndex").arg(&version.asset_index.id).arg("--accessToken").arg("0").arg("--versionType").arg("RMCL 0.1.0").status().unwrap();
}
解压natives
文件。
fn extract_jar(jar: &Path, dir: &Path) {if !dir.exists() {std::fs::create_dir_all(dir).unwrap();}let mut archive = zip::ZipArchive::new(std::fs::File::open(jar).unwrap()).unwrap();for i in 0..archive.len() {let mut entry = archive.by_index(i).unwrap();if entry.is_file() && !entry.name().contains("META-INF") {let mut name = entry.name();if name.contains("/") {name = &name[entry.name().rfind('/').unwrap() + 1..];}let path = dir.join(name);if path.exists() {std::fs::remove_file(&path).unwrap();}let mut file = std::fs::File::create(&path).unwrap();std::io::copy(&mut entry, &mut file).unwrap();}}
}
最后在main
函数中调用cli
函数。
fn main() {let matches = cli().get_matches();match matches.subcommand() {Some(("search", sub_matches)) => {search(sub_matches);}Some(("download", sub_matches)) => {download(sub_matches);}Some(("launch", sub_matches)) => {launch(sub_matches);}_ => unreachable!(),}
}
现在我们可以使用rmcl
命令来搜索、下载、启动游戏了。
项目地址