Rust入门实战 编写Minecraft启动器#5启动游戏

首发于Enaium的个人博客


好了,我们已经完成了所有的准备工作,现在我们可以开始编写启动游戏的代码了。

首先我们需要添加几个依赖。

toml 复制代码
model = { path = "../model" }
parse = { path = "../parse" }
download = { path = "../download" }
clap = { version = "4.5" }
zip = "2.1"

clap用于解析命令行参数,zip用于解压文件。

首先创建一个cli函数用于构建我们的命令行。

rust 复制代码
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函数用于获取游戏的所有版本清单。

rust 复制代码
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();
}

之后编写每个子命令的处理函数。

rust 复制代码
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);
    }
}
rust 复制代码
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);
    }
}
rust 复制代码
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文件。

rust 复制代码
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函数。

rust 复制代码
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命令来搜索、下载、启动游戏了。

项目地址

相关推荐
懒洋洋大魔王7 分钟前
7.Java高级编程 多线程
java·开发语言·jvm
=(^.^)=哈哈哈7 分钟前
Golang如何优雅的退出程序
开发语言·golang·xcode
学习使我变快乐9 分钟前
C++:用类实现链表,队列,栈
开发语言·c++·链表
茶馆大橘11 分钟前
【黑马点评】已解决java.lang.NullPointerException异常
java·开发语言
lmy_t15 分钟前
C++之第十二课
开发语言·c++
马剑威(威哥爱编程)19 分钟前
除了递归算法,要如何优化实现文件搜索功能
java·开发语言·算法·递归算法·威哥爱编程·memoization
我码玄黄30 分钟前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
MuseLss40 分钟前
HashMap高频面试知识点
java·开发语言·哈希算法
tyler-泰勒42 分钟前
初始c++:入门基础(完结)
java·开发语言·c++
憨憨小白1 小时前
函数的高级应用
开发语言·python·青少年编程·少儿编程