alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates server src save catalog.rs
use std::path::{Component, Path, PathBuf};
use super::{MCREGION_VERSION, SaveError, io_error, load_info, read_gzip, replace_level_dat};
#[derive(Clone, Debug, Default)]
pub struct Entry {
pub folder: String,
pub name: String,
pub last_played: i64,
pub size: i64,
pub problem: Option<String>,
pub needs_conversion: bool,
}
pub fn world_path(saves: &Path, folder: &str) -> Result<PathBuf, SaveError> {
let mut parts = Path::new(folder).components();
if !matches!(parts.next(), Some(Component::Normal(_))) || parts.next().is_some() {
return Err(SaveError::Invalid("Invalid world folder".into()));
}
let path = saves.join(folder);
match std::fs::symlink_metadata(&path) {
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => Err(
SaveError::Invalid("World folder must be a directory, not a link or file".into()),
),
Ok(_) => Ok(path),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(path),
Err(error) => Err(io_error(&path)(error)),
}
}
pub fn list(saves: &Path) -> Result<Vec<Entry>, SaveError> {
let entries = match std::fs::read_dir(saves) {
Ok(entries) => entries,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(error) => return Err(io_error(saves)(error)),
};
let mut worlds = Vec::new();
for entry in entries {
let entry = entry.map_err(io_error(saves))?;
let is_dir = entry.file_type().map_err(io_error(&entry.path()))?.is_dir();
let (true, Ok(folder)) = (is_dir, entry.file_name().into_string()) else {
continue;
};
let path = world_path(saves, &folder)?;
let mut world = Entry {
name: folder.clone(),
folder,
..Entry::default()
};
match load_info(&path) {
Ok(None) => continue,
Ok(Some(info)) => {
if !info.name.is_empty() {
world.name.clone_from(&info.name);
}
world.last_played = info.extra.long("LastPlayed");
world.size = info.size_on_disk;
world.needs_conversion = info.version == 0;
if !matches!(info.version, 0 | MCREGION_VERSION) {
world.problem = Some(format!("Unsupported save version: {}", info.version));
} else if !matches!(info.dimension(), -1 | 0) {
world.problem = Some(format!("Unsupported dimension: {}", info.dimension()));
}
}
Err(error) => world.problem = Some(error.to_string()),
}
worlds.push(world);
}
worlds.sort_by(|a, b| (b.last_played, &a.folder).cmp(&(a.last_played, &b.folder)));
Ok(worlds)
}
pub fn unused_folder(saves: &Path, name: &str) -> Result<String, SaveError> {
let mut folder: String = name
.trim()
.chars()
.map(|ch| {
if ch.is_control() || "/\\`?*<>|\":".contains(ch) {
'_'
} else {
ch
}
})
.collect();
if folder.is_empty() {
folder = "World".into();
}
if folder == "." || folder == ".." {
folder = folder.replace('.', "_");
}
loop {
match std::fs::symlink_metadata(saves.join(&folder)) {
Ok(_) => folder.push('-'),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(folder),
Err(error) => return Err(io_error(saves)(error)),
}
}
}
pub fn create(saves: &Path, name: &str) -> Result<(String, PathBuf), SaveError> {
std::fs::create_dir_all(saves).map_err(io_error(saves))?;
loop {
let folder = unused_folder(saves, name)?;
let path = world_path(saves, &folder)?;
match std::fs::create_dir(&path) {
Ok(()) => return Ok((folder, path)),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(error) => return Err(io_error(&path)(error)),
}
}
}
pub fn rename(saves: &Path, folder: &str, name: &str) -> Result<(), SaveError> {
if name.trim().is_empty() {
return Err(SaveError::Invalid("World name cannot be empty".into()));
}
let path = world_path(saves, folder)?;
let read = |file: &str| {
let root = read_gzip(&path.join(file))?;
let data = root.compound("Data").cloned();
data.map(|data| (root, data))
.ok_or_else(|| SaveError::Invalid("Missing world data".into()))
};
let (mut root, mut data) = read("level.dat").or_else(|_| read("level.dat_old"))?;
data.set_string("LevelName", name.trim());
root.set_compound("Data", data);
replace_level_dat(&path, &root)
}
pub fn delete(saves: &Path, folder: &str) -> Result<(), SaveError> {
let path = world_path(saves, folder)?;
std::fs::remove_dir_all(&path).map_err(io_error(&path))
}