diff --git a/src/main.rs b/src/main.rs index ca96ef8..4531587 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ use anyhow::Result as AResult; #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] struct Cli { + #[arg(long)] + path:Option, #[command(subcommand)] command: Commands, } @@ -14,7 +16,10 @@ struct Cli { enum Commands { Download(MapList), DownloadTextures(PathBufList), + ExtractTextures(PathBufList), ConvertTextures, + VPKContents, + BSPContents, DownloadMeshes(PathBufList), Extract(PathBufList), WriteAttributes, @@ -40,6 +45,9 @@ fn main() -> AResult<()> { match cli.command { Commands::Download(map_list)=>download(map_list.maps), Commands::DownloadTextures(pathlist)=>download_textures(pathlist.paths), + Commands::ExtractTextures(pathlist)=>extract_textures(vec![cli.path.unwrap()],pathlist.paths), + Commands::VPKContents=>vpk_contents(cli.path.unwrap()), + Commands::BSPContents=>bsp_contents(cli.path.unwrap()), Commands::ConvertTextures=>convert_textures(), Commands::DownloadMeshes(pathlist)=>download_meshes(pathlist.paths), Commands::Extract(pathlist)=>extract(pathlist.paths), @@ -1038,3 +1046,227 @@ fn write_attributes() -> AResult<()>{ } Ok(()) } + +enum VMTContent{ + VMT(String), + VTF(String), + Patch(vmt_parser::material::PatchMaterial), + Unsupported,//don't want to deal with whatever vmt variant + Unresolved,//could not locate a texture because of vmt content +} +impl VMTContent{ + fn vtf(opt:Option)->Self{ + match opt{ + Some(s)=>Self::VTF(s), + None=>Self::Unresolved, + } + } +} + +fn get_some_texture(material:vmt_parser::material::Material)->AResult{ + //just grab some texture from somewhere for now + Ok(match material{ + vmt_parser::material::Material::LightMappedGeneric(mat)=>VMTContent::vtf(Some(mat.base_texture)), + vmt_parser::material::Material::VertexLitGeneric(mat)=>VMTContent::vtf(mat.base_texture.or(mat.decal_texture)),//this just dies if there is none + vmt_parser::material::Material::VertexLitGenericDx6(mat)=>VMTContent::vtf(mat.base_texture.or(mat.decal_texture)), + vmt_parser::material::Material::UnlitGeneric(mat)=>VMTContent::vtf(mat.base_texture), + vmt_parser::material::Material::UnlitTwoTexture(mat)=>VMTContent::vtf(mat.base_texture), + vmt_parser::material::Material::Water(mat)=>VMTContent::vtf(mat.base_texture), + vmt_parser::material::Material::WorldVertexTransition(mat)=>VMTContent::vtf(Some(mat.base_texture)), + vmt_parser::material::Material::EyeRefract(mat)=>VMTContent::vtf(Some(mat.cornea_texture)), + vmt_parser::material::Material::SubRect(mat)=>VMTContent::VMT(mat.material),//recursive + vmt_parser::material::Material::Sprite(mat)=>VMTContent::vtf(Some(mat.base_texture)), + vmt_parser::material::Material::SpriteCard(mat)=>VMTContent::vtf(mat.base_texture), + vmt_parser::material::Material::Cable(mat)=>VMTContent::vtf(Some(mat.base_texture)), + vmt_parser::material::Material::Refract(mat)=>VMTContent::vtf(mat.base_texture), + vmt_parser::material::Material::Modulate(mat)=>VMTContent::vtf(Some(mat.base_texture)), + vmt_parser::material::Material::DecalModulate(mat)=>VMTContent::vtf(Some(mat.base_texture)), + vmt_parser::material::Material::Sky(mat)=>VMTContent::vtf(Some(mat.base_texture)), + vmt_parser::material::Material::Replacements(_mat)=>VMTContent::Unsupported, + vmt_parser::material::Material::Patch(mat)=>VMTContent::Patch(mat), + _=>return Err(anyhow::Error::msg("vmt failed to parse")), + }) +} + +fn get_vmtAResult>>>(find_stuff:&F,search_name:String)->AResult{ + if let Some(stuff)=find_stuff(search_name)?{ + //decode vmt and then write + let stuff=String::from_utf8(stuff)?; + let material=vmt_parser::from_str(stuff.as_str())?; + println!("vmt material={:?}",material); + return Ok(material); + } + Err(anyhow::Error::msg("vmt not found")) +} + +fn recursive_vmt_loaderAResult>>>(find_stuff:&F,material:vmt_parser::material::Material)->AResult>>{ + match get_some_texture(material)?{ + VMTContent::VMT(s)=>recursive_vmt_loader(find_stuff,get_vmt(find_stuff,s)?), + VMTContent::VTF(s)=>{ + let mut texture_file_name=std::path::PathBuf::from("materials"); + texture_file_name.push(s); + texture_file_name.set_extension("vtf"); + find_stuff(texture_file_name.into_os_string().into_string().unwrap()) + }, + VMTContent::Patch(mat)=>recursive_vmt_loader(find_stuff, + mat.resolve(|search_name|{ + match find_stuff(search_name.to_string())?{ + Some(bytes)=>Ok(String::from_utf8(bytes)?), + None=>Err(anyhow::Error::msg("could not find vmt")), + } + })? + ), + VMTContent::Unsupported=>{println!("Unsupported vmt");Ok(None)},//print and move on + VMTContent::Unresolved=>{println!("Unresolved vmt");Ok(None)}, + } +} + +fn extract_textures(paths:Vec,vpk_paths:Vec)->AResult<()>{ + let vpk_list:Vec=vpk_paths.into_iter().map(|vpk_path|vpk::VPK::read(&vpk_path).expect("vpk file does not exist")).collect(); + for path in paths{ + let mut deduplicate=std::collections::HashSet::new(); + let bsp=vbsp::Bsp::read(std::fs::read(path)?.as_ref())?; + for texture in bsp.textures(){ + deduplicate.insert(std::path::PathBuf::from(texture.name())); + } + //dedupe prop models + let mut model_dedupe=std::collections::HashSet::new(); + for prop in bsp.static_props(){ + model_dedupe.insert(prop.model()); + } + + //grab texture names from props + for model_name in model_dedupe{ + //.mdl, .vvd, .dx90.vtx + let mut path=std::path::PathBuf::from(model_name); + let file_name=std::path::PathBuf::from(path.file_stem().unwrap()); + path.pop(); + path.push(file_name); + let mut vvd_path=path.clone(); + let mut vtx_path=path.clone(); + vvd_path.set_extension("vvd"); + vtx_path.set_extension("dx90.vtx"); + match (bsp.pack.get(model_name),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){ + (Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{ + match (vmdl::mdl::Mdl::read(mdl_file.as_ref()),vmdl::vvd::Vvd::read(vvd_file.as_ref()),vmdl::vtx::Vtx::read(vtx_file.as_ref())){ + (Ok(mdl),Ok(vvd),Ok(vtx))=>{ + let model=vmdl::Model::from_parts(mdl,vtx,vvd); + for texture in model.textures(){ + for search_path in &texture.search_paths{ + let mut path=std::path::PathBuf::from(search_path.as_str()); + path.push(texture.name.as_str()); + deduplicate.insert(path); + } + } + }, + _=>println!("model_name={} error",model_name), + } + }, + _=>println!("no model name={}",model_name), + } + } + + let pack=&bsp.pack; + let vpk_list=&vpk_list; + std::thread::scope(move|s|{ + let mut thread_handles=Vec::new(); + for texture_name in deduplicate{ + let mut found_texture=false; + //LMAO imagine having to write type names + let write_image=|mut stuff,write_file_name|{ + let image=vtf::from_bytes(&mut stuff)?.highres_image.decode(0)?.to_rgba8(); + + let format=if image.width()%4!=0||image.height()%4!=0{ + image_dds::ImageFormat::R8G8B8A8Srgb + }else{ + image_dds::ImageFormat::BC7Srgb + }; + //this fails if the image dimensions are not a multiple of 4 + let dds = image_dds::dds_from_image( + &image, + format, + image_dds::Quality::Slow, + image_dds::Mipmaps::GeneratedAutomatic, + )?; + + //write dds + let mut dest=std::path::PathBuf::from("textures/dds"); + dest.push(write_file_name); + dest.set_extension("dds"); + std::fs::create_dir_all(dest.parent().unwrap())?; + let mut writer = std::io::BufWriter::new(std::fs::File::create(dest)?); + dds.write(&mut writer)?; + Ok::<(),anyhow::Error>(()) + }; + let find_stuff=|search_file_name:String|{ + println!("search_file_name={}",search_file_name); + match pack.get(search_file_name.as_str())?{ + Some(file)=>return Ok(Some(file)), + _=>(), + } + //search pak list + for vpk_index in vpk_list{ + if let Some(vpk_entry)=vpk_index.tree.get(search_file_name.as_str()){ + return Ok(Some(match vpk_entry.get()?{ + std::borrow::Cow::Borrowed(bytes)=>bytes.to_vec(), + std::borrow::Cow::Owned(bytes)=>bytes, + })); + } + } + Ok::>,anyhow::Error>(None) + }; + let loader=|texture_name:String|{ + let mut texture_file_name=std::path::PathBuf::from("materials"); + //lower case + let texture_file_name_lowercase=texture_name.to_lowercase(); + texture_file_name.push(texture_file_name_lowercase.clone()); + //remove stem and search for both vtf and vmt files + let stem=std::path::PathBuf::from(texture_file_name.file_stem().unwrap()); + texture_file_name.pop(); + texture_file_name.push(stem); + //somehow search for both files + let mut texture_file_name_vmt=texture_file_name.clone(); + texture_file_name.set_extension("vtf"); + texture_file_name_vmt.set_extension("vmt"); + if let Some(stuff)=find_stuff(texture_file_name.to_string_lossy().to_string())?{ + return Ok(Some(stuff)) + } + recursive_vmt_loader(&find_stuff,get_vmt(&find_stuff,texture_file_name_vmt.to_string_lossy().to_string())?) + }; + if let Some(stuff)=loader(texture_name.to_string_lossy().to_string())?{ + found_texture=true; + let texture_name=texture_name.clone(); + thread_handles.push(s.spawn(move||write_image(stuff,texture_name))); + } + if !found_texture{ + println!("no data"); + } + } + for thread in thread_handles{ + match thread.join(){ + Ok(Err(e))=>println!("write error: {:?}",e), + Err(e)=>println!("thread error: {:?}",e), + Ok(_)=>(), + } + } + Ok::<(),anyhow::Error>(()) + })? + } + Ok(()) +} + +fn vpk_contents(vpk_path:std::path::PathBuf)->AResult<()>{ + let vpk_index=vpk::VPK::read(&vpk_path)?; + for (label,entry) in vpk_index.tree.into_iter(){ + println!("vpk label={} entry={:?}",label,entry); + } + Ok(()) +} + +fn bsp_contents(path:std::path::PathBuf)->AResult<()>{ + let bsp=vbsp::Bsp::read(std::fs::read(path)?.as_ref())?; + for file_name in bsp.pack.into_zip().into_inner().unwrap().file_names(){ + println!("file_name={:?}",file_name); + } + Ok(()) +} \ No newline at end of file