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 / conversion.rs

use std::fs;
use std::io;
use std::path::{Path, PathBuf};

use common::world::ChunkPos;
use flate2::Compression;
use flate2::write::ZlibEncoder;

use super::{Regions, SaveError, SessionLock, chunk, io_error, read_gzip, sync};

/// `SaveConverterMcRegion.convertMapFormat`, reporting the percentage of chunks done.
pub(super) fn convert_chunks(
    dir: &Path,
    session: &SessionLock,
    progress: &dyn Fn(i32),
) -> Result<(), SaveError> {
    progress(0);
    let dimensions = [dir.to_owned(), dir.join("DIM-1")];
    let lists: Vec<_> = dimensions
        .iter()
        .map(|dir| collect(dir))
        .collect::<Result<_, _>>()?;
    let total = lists.iter().map(Vec::len).sum::<usize>().max(1);
    let mut done = 0;
    for (dimension, files) in dimensions.into_iter().zip(lists) {
        let mut regions = Regions {
            dir: dimension.join("region"),
            ..Regions::default()
        };
        for (pos, path) in files {
            session.verify()?;
            let root = read_gzip(&path)?;
            if chunk::read(&root, pos).is_none() {
                return Err(SaveError::Invalid(format!(
                    "{}: invalid legacy chunk",
                    path.display()
                )));
            }
            let region = regions.region(pos, true).map_err(io_error(&path))?.unwrap();
            if region.read(pos).map_err(io_error(&path))?.is_some() {
                done += 1;
                continue;
            }
            let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
            root.write(&mut encoder).map_err(io_error(&path))?;
            let compressed = encoder.finish().map_err(io_error(&path))?;
            session.verify()?;
            region.write(pos, &compressed).map_err(io_error(&path))?;
            done += 1;
            progress((100.0 * done as f64 / total as f64).round() as i32);
        }
        if regions.dir.exists() {
            sync(&regions.dir)?;
        }
    }
    Ok(())
}

fn collect(dir: &Path) -> Result<Vec<(ChunkPos, PathBuf)>, SaveError> {
    let mut files = Vec::new();
    for first in folders(dir)? {
        for second in folders(&first)? {
            for entry in fs::read_dir(&second).map_err(io_error(&second))? {
                let entry = entry.map_err(io_error(&second))?;
                if entry
                    .file_type()
                    .map_err(io_error(&entry.path()))?
                    .is_file()
                    && let Some(pos) = coordinates(&entry.file_name().to_string_lossy())
                {
                    files.push((pos, entry.path()));
                }
            }
        }
    }
    files.sort_unstable_by_key(|(pos, _)| (pos.x >> 5, pos.z >> 5, pos.x, pos.z));
    Ok(files)
}

fn folders(dir: &Path) -> Result<Vec<PathBuf>, SaveError> {
    let entries = match fs::read_dir(dir) {
        Ok(entries) => entries,
        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
        Err(error) => return Err(io_error(dir)(error)),
    };
    let mut paths = Vec::new();
    for entry in entries {
        let entry = entry.map_err(io_error(dir))?;
        let name = entry.file_name();
        let name = name.to_string_lossy();
        if (1..=2).contains(&name.len())
            && is_base36(&name)
            && entry.file_type().map_err(io_error(&entry.path()))?.is_dir()
        {
            paths.push(entry.path());
        }
    }
    Ok(paths)
}

fn is_base36(text: &str) -> bool {
    text.bytes()
        .all(|ch| ch.is_ascii_digit() || ch.is_ascii_lowercase())
}

fn coordinates(name: &str) -> Option<ChunkPos> {
    let mut parts = name.strip_prefix("c.")?.strip_suffix(".dat")?.split('.');
    let mut axis = || {
        let text = parts.next()?;
        let digits = text.strip_prefix('-').unwrap_or(text);
        if digits.is_empty() || !is_base36(digits) {
            return None;
        }
        i32::from_str_radix(text, 36).ok()
    };
    let pos = ChunkPos::new(axis()?, axis()?);
    parts.next().is_none().then_some(pos)
}