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 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(®ions.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)
}