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

项目地址

相关推荐
feiyangqingyun6 分钟前
关于无法下载Qt离线安装包的说明
开发语言·qt
三体世界12 分钟前
Linux --TCP协议实现简单的网络通信(中英翻译)
linux·c语言·开发语言·网络·c++·windows·tcp/ip
苕皮蓝牙土豆12 分钟前
Qt信号与槽机制深度解析
开发语言·qt
苕皮蓝牙土豆15 分钟前
Qt概述:基础组件的使用
开发语言·qt
zh_xuan30 分钟前
java Semaphore‌
java·开发语言
怀旧,1 小时前
【C语言】C语言经典小游戏:贪吃蛇(下)
c语言·开发语言·数据结构
余厌厌厌1 小时前
Golang学习之旅
开发语言·学习·golang
Code_流苏2 小时前
Python趣学篇:用Pygame打造绚烂流星雨动画
开发语言·python·pygame·图形编程·粒子系统·流星雨特效
clock的时钟2 小时前
c++面向对象第4天---拷贝构造函数与深复制
开发语言·c++
t198751282 小时前
Java连接Redis和基础操作命令
java·开发语言·redis