diff --git a/Cargo.lock b/Cargo.lock index db37065..0fb2922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -807,6 +807,7 @@ dependencies = [ "rbx_dom_weak 2.8.0", "rbx_reflection_database 0.2.11+roblox-634", "rbx_xml 0.13.4", + "strafesnet_bsp_loader", "strafesnet_deferred_loader", "strafesnet_rbx_loader", "strafesnet_snf", @@ -1391,6 +1392,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strafesnet_bsp_loader" +version = "0.1.3" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "6d4af68c422b5f57febbaa218f44ba02d413fd25e84afff9e45e557a8caee2ce" +dependencies = [ + "glam", + "strafesnet_common", + "vbsp", + "vmdl", +] + [[package]] name = "strafesnet_common" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 892ea18..7c352bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ rbx_binary = "0.7.1" rbx_dom_weak = "2.5.0" rbx_reflection_database = "0.2.7" rbx_xml = "0.13.1" +strafesnet_bsp_loader = { version = "0.1.3", registry = "strafesnet" } strafesnet_deferred_loader = { version = "0.3.1", features = ["legacy"], registry = "strafesnet" } strafesnet_rbx_loader = { version = "0.3.1", registry = "strafesnet" } strafesnet_snf = { version = "0.1.0", registry = "strafesnet" } diff --git a/src/main.rs b/src/main.rs index 371e783..1ca0180 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ struct Cli { #[derive(Subcommand)] enum Commands { RobloxToSNF(RobloxToSNFSubcommand), + SourceToSNF(SourceToSNFSubcommand), DownloadTextures(DownloadTexturesSubcommand), ExtractTextures(ExtractTexturesSubcommand), ConvertTextures(ConvertTexturesSubcommand), @@ -31,6 +32,13 @@ struct RobloxToSNFSubcommand { input_files:Vec, } #[derive(Args)] +struct SourceToSNFSubcommand { + #[arg(long)] + output_folder:PathBuf, + #[arg(required=true)] + input_files:Vec, +} +#[derive(Args)] struct DownloadTexturesSubcommand { #[arg(long,required=true)] roblox_files:Vec @@ -67,7 +75,8 @@ struct WriteAttributesSubcommand { fn main() -> AResult<()> { let cli = Cli::parse(); match cli.command { - Commands::RobloxToSNF(subcommand)=>convert_to_snf(subcommand.input_files,subcommand.output_folder), + Commands::RobloxToSNF(subcommand)=>roblox_to_snf(subcommand.input_files,subcommand.output_folder), + Commands::SourceToSNF(subcommand)=>source_to_snf(subcommand.input_files,subcommand.output_folder), Commands::DownloadTextures(subcommand)=>download_textures(subcommand.roblox_files), Commands::ExtractTextures(subcommand)=>extract_textures(vec![subcommand.bsp_file],subcommand.vpk_dir_files), Commands::VPKContents(subcommand)=>vpk_contents(subcommand.input_file), @@ -646,6 +655,7 @@ enum ConvertError{ IO(std::io::Error), SNFMap(strafesnet_snf::map::Error), RbxLoader(strafesnet_rbx_loader::ReadError), + BspLoader(strafesnet_bsp_loader::ReadError), } impl std::fmt::Display for ConvertError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ @@ -656,7 +666,7 @@ impl std::error::Error for ConvertError{} type MapThread=std::thread::JoinHandle>; -fn convert_to_snf(pathlist:Vec,output_folder:PathBuf)->AResult<()>{ +fn roblox_to_snf(pathlist:Vec,output_folder:PathBuf)->AResult<()>{ let n_paths=pathlist.len(); let start = std::time::Instant::now(); let mut threads:std::collections::VecDeque=std::collections::VecDeque::new(); @@ -724,3 +734,78 @@ fn convert_to_snf(pathlist:Vec,output_folder:PathBuf)->AResu println!("{:?}", start.elapsed()); Ok(()) } + +fn source_to_snf(pathlist:Vec,output_folder:PathBuf)->AResult<()>{ + let n_paths=pathlist.len(); + let start = std::time::Instant::now(); + let mut threads:std::collections::VecDeque=std::collections::VecDeque::new(); + let mut i=0; + let mut join_thread=|thread:MapThread|{ + i+=1; + if let Err(e)=thread.join(){ + println!("thread error: {:?}",e); + }else{ + println!("{}/{}",i,n_paths); + } + }; + for path in pathlist{ + if 32<=threads.len(){ + join_thread(threads.pop_front().unwrap()); + } + let output_folder=output_folder.clone(); + threads.push_back(std::thread::spawn(move ||{ + let bsp=strafesnet_bsp_loader::read( + std::fs::File::open(path.as_path()) + .map_err(ConvertError::IO)? + ).map_err(ConvertError::BspLoader)?; + let mut loader=strafesnet_deferred_loader::source_legacy(); + + let (texture_loader,mesh_loader)=loader.get_inner_mut(); + + let map_step1=strafesnet_bsp_loader::convert( + &bsp, + |name|texture_loader.acquire_render_config_id(name), + |name|mesh_loader.acquire_mesh_id(name), + ); + + let prop_meshes=mesh_loader.load_meshes(&bsp.as_ref()); + + let map_step2=map_step1.add_prop_meshes( + //the type conflagulator 9000 + prop_meshes.into_iter().map(|(mesh_id,loader_model)| + (mesh_id,strafesnet_bsp_loader::data::ModelData{ + mdl:strafesnet_bsp_loader::data::MdlData::new(loader_model.mdl.get()), + vtx:strafesnet_bsp_loader::data::VtxData::new(loader_model.vtx.get()), + vvd:strafesnet_bsp_loader::data::VvdData::new(loader_model.vvd.get()), + }) + ), + |name|texture_loader.acquire_render_config_id(name), + ); + + let (textures,render_configs)=loader.into_render_configs().map_err(ConvertError::IO)?.consume(); + + let map=map_step2.add_render_configs_and_textures( + render_configs.into_iter(), + textures.into_iter().map(|(texture_id,texture)| + (texture_id,match texture{ + strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data, + }) + ), + ); + + let mut dest=output_folder.clone(); + dest.push(path.file_stem().unwrap()); + dest.set_extension("snfm"); + let file=std::fs::File::create(dest).map_err(ConvertError::IO)?; + + strafesnet_snf::map::write_map(file,map).map_err(ConvertError::SNFMap)?; + Ok(()) + })); + } + + for thread in threads{ + join_thread(thread); + } + println!("{:?}", start.elapsed()); + Ok(()) +}