Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
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
Download file

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))
}