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 client src assets packs.rs
use std::ffi::OsString;
use std::fs::{DirEntry, File};
use std::io::{self, Read};
use std::path::{Component, Path, PathBuf};
use std::time::SystemTime;
use super::{AssetError, RgbaImage, builtin, decode};
const MAX_ASSET_BYTES: u64 = 64 << 20;
pub struct Pack {
pub name: String,
pub description: [String; 2],
pub thumbnail: RgbaImage,
}
/// A pack file's name, size and modification time, to notice changes.
pub type Stamp = (OsString, u64, Option<SystemTime>);
pub fn path(directory: &Path, name: &str) -> Result<Option<PathBuf>, AssetError> {
if name == "Default" {
return Ok(None);
}
let mut components = Path::new(name).components();
if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {
return Err(AssetError::Pack("Invalid texture pack name".into()));
}
Ok(Some(directory.join(name)))
}
pub fn list(directory: &Path, defaults: &Path) -> Result<Vec<Pack>, AssetError> {
let mut packs = vec![Pack {
name: "Default".into(),
description: ["The default look of Minecraft".into(), String::new()],
thumbnail: builtin(defaults, "pack.png")?,
}];
let zips = zips(directory).map_err(|source| AssetError::Read {
path: directory.into(),
source,
})?;
for path in zips.iter().map(DirEntry::path) {
let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
continue;
};
let opened = Source::open(defaults, Some(&path))
.and_then(|mut source| Ok((source.read_override("pack.txt")?, source)));
let (text, mut source) = match opened {
Ok((text, source)) => (
String::from_utf8_lossy(&text.unwrap_or_default()).into_owned(),
source,
),
Err(error) => {
tracing::warn!("cannot read texture pack {}: {error}", path.display());
continue;
}
};
let mut lines = text.lines();
let description =
std::array::from_fn(|_| lines.next().unwrap_or("").chars().take(34).collect());
let thumbnail = source
.read_override("pack.png")
.and_then(|bytes| match bytes {
Some(bytes) => decode("pack.png", &bytes),
None => builtin(defaults, "gui/unknown_pack.png"),
})
.unwrap_or_else(|_| packs[0].thumbnail.clone());
packs.push(Pack {
name: name.into(),
description,
thumbnail,
});
}
Ok(packs)
}
/// The `.zip` files in `directory`, by name.
fn zips(directory: &Path) -> io::Result<Vec<DirEntry>> {
let entries = match std::fs::read_dir(directory) {
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
entries => entries?,
};
let mut zips = Vec::new();
for entry in entries {
let entry = entry?;
if entry
.path()
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
{
zips.push(entry);
}
}
zips.sort_by_key(DirEntry::file_name);
Ok(zips)
}
pub fn stamps(directory: &Path) -> io::Result<Vec<Stamp>> {
zips(directory)?
.into_iter()
.map(|entry| {
let metadata = entry.metadata()?;
Ok((entry.file_name(), metadata.len(), metadata.modified().ok()))
})
.collect()
}
pub(super) fn read_default(defaults: &Path, name: &str) -> Result<Vec<u8>, AssetError> {
let path = defaults.join(name);
std::fs::read(&path).map_err(|source| AssetError::Read { path, source })
}
pub(super) struct Source<'a> {
defaults: &'a Path,
archive: Option<zip::ZipArchive<File>>,
}
impl<'a> Source<'a> {
pub fn open(defaults: &'a Path, pack: Option<&Path>) -> Result<Self, AssetError> {
let archive = pack
.map(|path| {
let file = File::open(path).map_err(|source| AssetError::Read {
path: path.into(),
source,
})?;
zip::ZipArchive::new(file).map_err(|error| AssetError::Pack(error.to_string()))
})
.transpose()?;
Ok(Self { defaults, archive })
}
/// The file from the texture pack, if it has one.
fn read_override(&mut self, name: &str) -> Result<Option<Vec<u8>>, AssetError> {
let Some(archive) = &mut self.archive else {
return Ok(None);
};
let mut file = match archive.by_name(name) {
Ok(file) => file,
Err(zip::result::ZipError::FileNotFound) => return Ok(None),
Err(error) => return Err(AssetError::Pack(error.to_string())),
};
if file.size() > MAX_ASSET_BYTES {
return Err(AssetError::Pack(format!(
"Texture pack asset {name} is too large"
)));
}
let mut bytes = Vec::with_capacity(file.size() as usize);
file.read_to_end(&mut bytes)
.map_err(|error| AssetError::Pack(error.to_string()))?;
Ok(Some(bytes))
}
pub fn read(&mut self, name: &str) -> Result<Vec<u8>, AssetError> {
match self.read_override(name)? {
Some(bytes) => Ok(bytes),
None => read_default(self.defaults, name),
}
}
}