feat(debug): 支持网易 Minecraft 调试启动

新增 debug 子命令,自动准备开发世界、注册内置调试 MOD,并将项目行为包和资源包链接到网易运行目录,方便启动游戏后直接进入调试世界。

调试 MOD 资源随仓库一起嵌入,避免依赖本机绝对路径;Windows junction 写入剥离 verbatim 前缀后的 DOS 路径,保证 Minecraft 能正确读取链接包。
This commit is contained in:
2026-05-16 00:59:51 +08:00
parent de2b804aad
commit 011b59c948
36 changed files with 4138 additions and 3 deletions

View File

@@ -6,10 +6,21 @@ use std::{
const EMPTY_DIRS_FILE: &str = ".empty-dirs";
const DEBUG_MOD_ROOT: &str = "debug_mod";
fn main() {
println!("cargo:rerun-if-changed=examples");
println!("cargo:rerun-if-changed={}", DEBUG_MOD_ROOT);
let examples_root = PathBuf::from("examples");
let debug_mod_root = PathBuf::from(DEBUG_MOD_ROOT);
if !debug_mod_root.is_dir() {
panic!(
"required debug MOD resource directory was not found: {}",
DEBUG_MOD_ROOT
);
}
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is not set"));
let out_file = out_dir.join("embedded_examples.rs");
@@ -17,6 +28,8 @@ fn main() {
let mut files = Vec::new();
let mut empty_dirs_by_example = BTreeMap::new();
let mut debug_mod_files = Vec::new();
if examples_root.is_dir() {
collect_examples(
&examples_root,
@@ -27,6 +40,8 @@ fn main() {
)
.expect("failed to collect example templates");
}
collect_files(&debug_mod_root, &debug_mod_root, &mut debug_mod_files)
.expect("failed to collect debug MOD resources");
dirs.sort();
dirs.dedup();
@@ -35,6 +50,7 @@ fn main() {
empty_dirs.sort();
empty_dirs.dedup();
}
debug_mod_files.sort_by(|a, b| a.0.cmp(&b.0));
let mut generated = String::new();
generated.push_str(
@@ -45,7 +61,8 @@ fn main() {
generated.push_str(&format!(" {:?},\n", dir));
}
generated.push_str("];\n\n");
generated.push_str("#[allow(dead_code)]\nstatic EMBEDDED_EXAMPLE_FILES: &[EmbeddedFile] = &[\n");
generated
.push_str("#[allow(dead_code)]\nstatic EMBEDDED_EXAMPLE_FILES: &[EmbeddedFile] = &[\n");
for (relative_path, absolute_path) in &files {
generated.push_str(&format!(
" EmbeddedFile {{ path: {:?}, contents: include_bytes!(r#\"{}\"#) }},\n",
@@ -70,9 +87,39 @@ fn main() {
}
generated.push_str(" ] },\n");
}
generated.push_str("];\n\n");
generated
.push_str("#[allow(dead_code)]\nstatic EMBEDDED_DEBUG_MOD_FILES: &[EmbeddedFile] = &[\n");
for (relative_path, absolute_path) in &debug_mod_files {
generated.push_str(&format!(
" EmbeddedFile {{ path: {:?}, contents: include_bytes!(r#\"{}\"#) }},\n",
relative_path,
absolute_path.display()
));
}
generated.push_str("];\n");
fs::write(out_file, generated).expect("failed to write embedded example list");
fs::write(out_file, generated).expect("failed to write embedded resource list");
}
fn collect_files(root: &Path, dir: &Path, files: &mut Vec<(String, PathBuf)>) -> io::Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
collect_files(root, &path, files)?;
} else if path.is_file() {
let relative_path = path
.strip_prefix(root)
.expect("embedded resource path is outside root")
.to_string_lossy()
.replace('\\', "/");
files.push((relative_path, path.canonicalize()?));
}
}
Ok(())
}
fn collect_examples(