use super::{BlockModel, BlockstateDefinition, ResourcePack, TextureData};
use crate::error::{MesherError, Result};
use crate::resource_pack::texture::load_texture_from_bytes;
use std::io::Read;
use std::path::Path;
pub fn load_from_path<P: AsRef<Path>>(path: P) -> Result<ResourcePack> {
let path = path.as_ref();
if path.is_dir() {
load_from_directory(path)
} else {
let data = std::fs::read(path)?;
load_from_bytes(&data)
}
}
pub fn load_from_bytes(data: &[u8]) -> Result<ResourcePack> {
let cursor = std::io::Cursor::new(data);
let mut archive = zip::ZipArchive::new(cursor)?;
let mut pack = ResourcePack::new();
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let file_path = file.name().to_string();
if file.is_dir() {
continue;
}
if let Some((namespace, asset_type, asset_path)) = parse_asset_path(&file_path) {
match asset_type {
"blockstates" => {
if asset_path.ends_with(".json") {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let block_id = asset_path.trim_end_matches(".json");
match serde_json::from_str::<BlockstateDefinition>(&contents) {
Ok(def) => {
pack.add_blockstate(namespace, block_id, def);
}
Err(e) => {
eprintln!(
"Warning: Failed to parse blockstate {}/{}: {}",
namespace, block_id, e
);
}
}
}
}
"models" => {
if asset_path.ends_with(".json") {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let model_path = asset_path.trim_end_matches(".json");
match serde_json::from_str::<BlockModel>(&contents) {
Ok(model) => {
pack.add_model(namespace, model_path, model);
}
Err(e) => {
eprintln!(
"Warning: Failed to parse model {}/{}: {}",
namespace, model_path, e
);
}
}
}
}
"textures" => {
if asset_path.ends_with(".png") {
let mut data = Vec::new();
file.read_to_end(&mut data)?;
let texture_path = asset_path.trim_end_matches(".png");
match load_texture_from_bytes(&data) {
Ok(texture) => {
pack.add_texture(namespace, texture_path, texture);
}
Err(e) => {
eprintln!(
"Warning: Failed to load texture {}/{}: {}",
namespace, texture_path, e
);
}
}
}
}
_ => {}
}
}
}
Ok(pack)
}
fn load_from_directory(path: &Path) -> Result<ResourcePack> {
let mut pack = ResourcePack::new();
let assets_path = path.join("assets");
if !assets_path.exists() {
return Err(MesherError::InvalidResourcePack(
"No assets directory found".to_string(),
));
}
for namespace_entry in std::fs::read_dir(&assets_path)? {
let namespace_entry = namespace_entry?;
if !namespace_entry.file_type()?.is_dir() {
continue;
}
let namespace = namespace_entry
.file_name()
.to_string_lossy()
.to_string();
let namespace_path = namespace_entry.path();
let blockstates_path = namespace_path.join("blockstates");
if blockstates_path.exists() {
load_json_files(&blockstates_path, &namespace, |block_id, contents| {
if let Ok(def) = serde_json::from_str::<BlockstateDefinition>(contents) {
pack.add_blockstate(&namespace, block_id, def);
}
})?;
}
let models_path = namespace_path.join("models");
if models_path.exists() {
load_json_files_recursive(&models_path, &models_path, &namespace, &mut |model_path, contents| {
if let Ok(model) = serde_json::from_str::<BlockModel>(contents) {
pack.add_model(&namespace, model_path, model);
}
})?;
}
let textures_path = namespace_path.join("textures");
if textures_path.exists() {
load_texture_files_recursive(&textures_path, &textures_path, &namespace, &mut |texture_path, data| {
if let Ok(texture) = load_texture_from_bytes(data) {
pack.add_texture(&namespace, texture_path, texture);
}
})?;
}
}
Ok(pack)
}
fn parse_asset_path(file_path: &str) -> Option<(&str, &str, &str)> {
let parts: Vec<&str> = file_path.splitn(4, '/').collect();
if parts.len() >= 4 && parts[0] == "assets" {
Some((parts[1], parts[2], parts[3]))
} else {
None
}
}
fn load_json_files<F>(dir: &Path, namespace: &str, mut handler: F) -> Result<()>
where
F: FnMut(&str, &str),
{
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().map(|e| e == "json").unwrap_or(false) {
let file_name = path
.file_stem()
.unwrap()
.to_string_lossy()
.to_string();
let contents = std::fs::read_to_string(&path)?;
handler(&file_name, &contents);
}
}
Ok(())
}
fn load_json_files_recursive<F>(
base: &Path,
dir: &Path,
namespace: &str,
handler: &mut F,
) -> Result<()>
where
F: FnMut(&str, &str),
{
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
load_json_files_recursive(base, &path, namespace, handler)?;
} else if path.extension().map(|e| e == "json").unwrap_or(false) {
let relative = path
.strip_prefix(base)
.unwrap()
.with_extension("")
.to_string_lossy()
.replace('\\', "/");
let contents = std::fs::read_to_string(&path)?;
handler(&relative, &contents);
}
}
Ok(())
}
fn load_texture_files_recursive<F>(
base: &Path,
dir: &Path,
namespace: &str,
handler: &mut F,
) -> Result<()>
where
F: FnMut(&str, &[u8]),
{
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
load_texture_files_recursive(base, &path, namespace, handler)?;
} else if path.extension().map(|e| e == "png").unwrap_or(false) {
let relative = path
.strip_prefix(base)
.unwrap()
.with_extension("")
.to_string_lossy()
.replace('\\', "/");
let data = std::fs::read(&path)?;
handler(&relative, &data);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_asset_path() {
assert_eq!(
parse_asset_path("assets/minecraft/blockstates/stone.json"),
Some(("minecraft", "blockstates", "stone.json"))
);
assert_eq!(
parse_asset_path("assets/minecraft/models/block/stone.json"),
Some(("minecraft", "models", "block/stone.json"))
);
assert_eq!(
parse_asset_path("assets/mymod/textures/block/custom.png"),
Some(("mymod", "textures", "block/custom.png"))
);
assert_eq!(parse_asset_path("pack.mcmeta"), None);
assert_eq!(parse_asset_path("data/minecraft/recipes/test.json"), None);
}
}