From 8ef65376b8f5395f3987dfedc2e37e6091f49125 Mon Sep 17 00:00:00 2001 From: Piotr Siuszko Date: Thu, 28 Dec 2023 20:41:49 +0100 Subject: [PATCH] Find texture path now missing part is reading gltf file, changing materials texture and saving it --- Cargo.toml | 1 + src/asset.rs | 70 ++++++++++++++++++++++++++------ src/unpacker.rs | 105 ++++++++++++++++++++++++++++++------------------ 3 files changed, 125 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 11e4528..16d68ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,5 @@ clap = { version = "4.4", features = ["derive"] } flate2 = "1.0" gltf = "1.4.0" rayon = "1.8.0" +regex = "1.10.2" tar = "0.4" diff --git a/src/asset.rs b/src/asset.rs index 99451e3..29a8412 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -1,3 +1,4 @@ +use regex::Regex; use std::ffi::OsStr; use std::fs; use std::fs::DirEntry; @@ -5,32 +6,71 @@ use std::fs::File; use std::io::BufRead; use std::io::BufReader; use std::path::Path; +use std::path::PathBuf; #[derive(Clone)] pub struct Asset { pub extension: Option, - pub hash: String, - pub path_name: String, + pub guid: String, + pub path: String, pub has_meta: bool, - pub asset_type: AssetType + pub asset_type: AssetType, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub enum AssetType{ +pub enum AssetType { FbxModel, Material, Prefab, Scene, - Other(String) + Other(String), } impl Asset { - pub fn from_path(entry: &DirEntry) -> Option { + pub fn try_get_mat_texture_guid(&self) -> Option { + match &self.asset_type { + AssetType::Material => {} + _ => return None, + } + let file = File::open(&self.path).unwrap(); + let buf_reader = BufReader::new(file); + let search = buf_reader.lines().into_iter().find(|s| { + let ss = s.as_ref().unwrap(); + return ss.contains("m_Texture") && ss.contains("guid: "); + }); + if let Some(line) = search { + let line = line.unwrap_or_default(); + return extract_guid(&line); + } + None + } + + pub fn prepare_directory(&self) { + println!("{}: {:?}", self.guid, self.path); + let base_path = Path::new(&self.path); + let result_dir = base_path.parent(); + if result_dir.is_none() { + eprintln!("{} is none", &self.path); + } + let result_dir = result_dir.unwrap(); + if !result_dir.exists() { + let result = fs::create_dir_all(result_dir); + if result.is_err() { + eprintln!( + "Error {}: {}", + result_dir.to_str().unwrap(), + result.err().unwrap() + ); + } + } + } + + pub fn from_path(entry: &DirEntry, output_dir: &PathBuf) -> Option { let root_file = entry.path(); if !root_file.is_dir() { return None; } - let asset = entry.file_name().into_string().unwrap(); + let guid = entry.file_name().into_string().unwrap(); let mut real_path = String::new(); let mut extension = None; let mut has_asset = false; @@ -46,7 +86,7 @@ impl Asset { let line = buf_reader.lines().next(); match line { Some(Ok(path)) => { - real_path = path; + real_path = output_dir.join(path).to_str().unwrap().to_string(); if let Some(e) = Path::new(&real_path).extension().and_then(OsStr::to_str) { @@ -68,14 +108,14 @@ impl Asset { "prefab" => AssetType::Prefab, "unity" => AssetType::Scene, "mat" => AssetType::Material, - _ => AssetType::Other(str.clone()) + _ => AssetType::Other(str.clone()), }, - _ => AssetType::Other(String::new()) + _ => AssetType::Other(String::new()), }; Some(Asset { extension, - hash: asset, - path_name: real_path, + guid, + path: real_path, has_meta, asset_type, }) @@ -84,3 +124,9 @@ impl Asset { } } } + +fn extract_guid(text: &str) -> Option { + let re = Regex::new(r"guid: (?P[A-Za-z0-9]{32})").unwrap(); + re.captures(text) + .and_then(|cap| cap.name("guid").map(|guid| guid.as_str().to_string())) +} diff --git a/src/unpacker.rs b/src/unpacker.rs index 12ff41a..2129367 100644 --- a/src/unpacker.rs +++ b/src/unpacker.rs @@ -1,4 +1,4 @@ -use crate::asset::{Asset,AssetType}; +use crate::asset::{Asset, AssetType}; use flate2::read::GzDecoder; use rayon::prelude::*; use std::fs::File; @@ -7,7 +7,6 @@ use std::process::Command; use std::sync::mpsc::channel; use std::sync::Arc; use std::{fs, io}; -use std::hash::Hash; use tar::Archive; #[derive(Clone)] @@ -36,6 +35,7 @@ impl Unpacker { pub fn extract(&mut self) { let archive_path = Path::new(&self.args.input); let tmp_path = Path::new("./tmp_dir"); + let output_dir = Path::new(&self.args.output); if let Err(e) = Unpacker::extract_archive(archive_path, tmp_path) { println!("Failed to extract archive: {}", e); @@ -49,7 +49,7 @@ impl Unpacker { .par_bridge() .for_each_with(sender, |s, entry| { let entry = entry.unwrap(); - let asset = crate::asset::Asset::from_path(&entry); + let asset = crate::asset::Asset::from_path(&entry, &output_dir.to_path_buf()); if let Some(asset) = asset { let extension = &asset.extension.clone().unwrap_or_default(); if !ignored_extensions.contains(extension) { @@ -64,29 +64,67 @@ impl Unpacker { if self.args.fbx_to_gltf.is_none() { return; } - let output_dir = Path::new(&self.args.output); - - let fbx_models : Vec = self.assets.clone().into_iter().filter(|a| &a.asset_type == &AssetType::FbxModel).collect(); - let prefabs : Vec = self.assets.clone().into_iter().filter(|a| &a.asset_type == &AssetType::Prefab).collect(); - let materials : Vec = self.assets.clone().into_iter().filter(|a| &a.asset_type == &AssetType::Material).collect(); - println!("There are {} models, {} prefabs and {} materials", fbx_models.len(), prefabs.len(), materials.len()); + let fbx_models: Vec = self + .assets + .clone() + .into_iter() + .filter(|a| &a.asset_type == &AssetType::FbxModel) + .collect(); + let prefabs: Vec = self + .assets + .clone() + .into_iter() + .filter(|a| &a.asset_type == &AssetType::Prefab) + .collect(); + let materials: Vec = self + .assets + .clone() + .into_iter() + .filter(|a| &a.asset_type == &AssetType::Material) + .collect(); + println!( + "There are {} models, {} prefabs and {} materials", + fbx_models.len(), + prefabs.len(), + materials.len() + ); + let mut counter = 0; + let mut result_models : Vec = vec![]; for prefab in prefabs.iter() { - let path = Path::new(&prefab.path_name); - let result_path = output_dir.join(path); - let prefab_content = fs::read_to_string(&result_path).unwrap(); - let matching_materials : Vec = materials.clone().into_iter().filter(|a| prefab_content.contains(&a.hash)).collect(); - let matching_models : Vec = fbx_models.clone().into_iter().filter(|a| prefab_content.contains(&a.hash)).collect(); - println!("Prefab: {},\nMaterials: ",&prefab.path_name); - for m in matching_materials.iter() { - println!(" - {}",&m.path_name); + let path = Path::new(&prefab.path); + let prefab_content = fs::read_to_string(&path).unwrap(); + let matching_materials: Vec = materials + .clone() + .into_iter() + .filter(|a| prefab_content.contains(&a.guid)) + .collect(); + let matching_models: Vec = fbx_models + .clone() + .into_iter() + .filter(|a| prefab_content.contains(&a.guid)) + .collect(); + if matching_materials.len() != 1 || 1 != matching_models.len() { + continue; } - println!("Models: "); - for m in matching_models.iter() { - println!(" - {}",&m.path_name); + let material = matching_materials.first().unwrap(); + let model = matching_models.first().unwrap(); + if result_models.iter().any(|a| model.guid.eq(&a.guid)) { + continue; } + let texture_guid: Option = material.try_get_mat_texture_guid(); + let texture_asset: &Asset; + match &texture_guid { + Some(guid) => { + texture_asset = self.assets.iter().find(|a| guid.eq(&a.guid)).unwrap() + } + None => continue, + } + // here we should read gltf file and replace material texture with Uri based on texture_asset - // now if there is one material and one model we should read material texture path and assign it to model material texture + result_models.push(model.clone()); + counter += 1; } + println!("Updated {} models", counter); } pub fn process_data(&self) { @@ -97,21 +135,18 @@ impl Unpacker { let mapping_arc = Arc::new(&self.assets); let tmp_dir = Arc::new(tmp_path); fs::create_dir(output_dir).unwrap(); - let output_dir = Arc::new(output_dir); mapping_arc.par_iter().for_each(|asset| { - let asset_hash = &asset.hash; - let path = Path::new(&asset.path_name); + let asset_hash = &asset.guid; + let path = Path::new(&asset.path); let source_asset = Path::new(&*tmp_dir).join(asset_hash).join("asset"); - let result_path = output_dir.join(path); - process_directory(asset_hash, &asset.path_name, &result_path); + asset.prepare_directory(); if copy_meta_files && asset.has_meta { let source_meta = Path::new(&*tmp_dir).join(asset_hash).join("asset.meta"); - let mut meta_path = asset.path_name.clone(); + let mut meta_path = asset.path.clone(); meta_path.push_str(".meta"); - let result_path = output_dir.join(meta_path); - fs::rename(source_meta, result_path).unwrap(); + fs::rename(source_meta, meta_path).unwrap(); } if !source_asset.exists() { @@ -121,24 +156,16 @@ impl Unpacker { if self.args.fbx_to_gltf.is_some() && &asset.asset_type == &AssetType::FbxModel { process_fbx_file( &source_asset, - &result_path, + &path, &self.args.fbx_to_gltf.clone().unwrap(), ); } else { - process_non_fbx_file(&source_asset, &result_path); + process_non_fbx_file(&source_asset, &path); } }); fs::remove_dir_all(Path::new(&*tmp_dir)).unwrap(); - fn process_directory(asset_hash: &str, asset_path: &str, result_path: &Path) { - println!("{}: {:?}", asset_hash, asset_path); - let result_dir = result_path.parent().unwrap(); - if !result_dir.exists() { - fs::create_dir_all(result_dir).unwrap(); - } - } - fn process_fbx_file(source_asset: &Path, result_path: &Path, tool: &PathBuf) { let out_path = result_path.with_extension(""); println!(