From a5079f21d72a7fa199787918637fa5780cea56bb Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Mon, 27 Jan 2025 08:47:51 -0800
Subject: [PATCH] wip download_assets (pre-tokio)

---
 src/roblox.rs | 185 +++++++++++++++++++++-----------------------------
 1 file changed, 76 insertions(+), 109 deletions(-)

diff --git a/src/roblox.rs b/src/roblox.rs
index 53a7a5b..b22b62f 100644
--- a/src/roblox.rs
+++ b/src/roblox.rs
@@ -1,5 +1,5 @@
-use std::path::PathBuf;
-use std::io::{Read,Seek};
+use std::path::{Path,PathBuf};
+use std::io::{Cursor,Read,Seek};
 use std::collections::HashSet;
 use clap::{Args,Subcommand};
 use anyhow::Result as AResult;
@@ -9,8 +9,7 @@ use strafesnet_deferred_loader::rbxassetid::RobloxAssetId;
 #[derive(Subcommand)]
 pub enum Commands{
 	RobloxToSNF(RobloxToSNFSubcommand),
-	DownloadTextures(DownloadTexturesSubcommand),
-	DownloadMeshes(DownloadMeshesSubcommand),
+	DownloadAssets(DownloadAssetsSubcommand),
 }
 
 #[derive(Args)]
@@ -21,12 +20,7 @@ pub struct RobloxToSNFSubcommand {
 	input_files:Vec<PathBuf>,
 }
 #[derive(Args)]
-pub struct DownloadTexturesSubcommand {
-	#[arg(long,required=true)]
-	roblox_files:Vec<PathBuf>
-}
-#[derive(Args)]
-pub struct DownloadMeshesSubcommand {
+pub struct DownloadAssetsSubcommand{
 	#[arg(long,required=true)]
 	roblox_files:Vec<PathBuf>
 }
@@ -35,27 +29,19 @@ impl Commands{
 	pub fn run(self)->AResult<()>{
 		match self{
 			Commands::RobloxToSNF(subcommand)=>roblox_to_snf(subcommand.input_files,subcommand.output_folder),
-			Commands::DownloadTextures(subcommand)=>download_textures(subcommand.roblox_files),
-			Commands::DownloadMeshes(subcommand)=>download_meshes(subcommand.roblox_files),
+			Commands::DownloadAssets(subcommand)=>download_assets(subcommand.roblox_files),
 		}
 	}
 }
 
-fn load_dom<R:Read+Seek>(input:&mut R)->AResult<rbx_dom_weak::WeakDom>{
+fn load_dom<R:Read+Seek>(mut input:R)->AResult<rbx_dom_weak::WeakDom>{
 	let mut first_8=[0u8;8];
-	if let (Ok(()),Ok(()))=(std::io::Read::read_exact(input, &mut first_8),std::io::Seek::rewind(input)){
-		match &first_8[0..4]{
-			b"<rob"=>{
-				match &first_8[4..8]{
-					b"lox!"=>rbx_binary::from_reader(input).map_err(anyhow::Error::msg),
-					b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(anyhow::Error::msg),
-					other=>Err(anyhow::Error::msg(format!("Unknown Roblox file type {:?}",other))),
-				}
-			},
-			_=>Err(anyhow::Error::msg("unsupported file type")),
-		}
-	}else{
-		Err(anyhow::Error::msg("peek failed"))
+	input.read_exact(&mut first_8)?;
+	input.rewind()?;
+	match &first_8{
+		b"<roblox!"=>rbx_binary::from_reader(input).map_err(anyhow::Error::msg),
+		b"<roblox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(anyhow::Error::msg),
+		_=>Err(anyhow::Error::msg("unsupported file type")),
 	}
 }
 
@@ -85,10 +71,11 @@ SurfaceAppearance.NormalMap
 SurfaceAppearance.RoughnessMap
 SurfaceAppearance.TexturePack
 */
-fn accumulate_content_id(content_list:&mut HashSet<u64>,object:&Instance,property:&str){
+fn accumulate_content_id(content_list:&mut HashSet<RobloxAssetId>,object:&Instance,property:&str){
 	if let Some(rbx_dom_weak::types::Variant::Content(content))=object.properties.get(property){
-		if let Ok(asset_id)=AsRef::<str>::as_ref(content).parse::<RobloxAssetId>(){
-			content_list.insert(asset_id.0);
+		let url:&str=content.as_ref();
+		if let Ok(asset_id)=url.parse(){
+			content_list.insert(asset_id);
 		}else{
 			println!("Content failed to parse into AssetID: {:?}",content);
 		}
@@ -96,90 +83,70 @@ fn accumulate_content_id(content_list:&mut HashSet<u64>,object:&Instance,propert
 		println!("property={} does not exist for class={}",object.class.as_str(),property);
 	}
 }
-fn download_textures(paths:Vec<PathBuf>)->AResult<()>{
-	println!("Reading files, this could take a hot minute...");
-	let mut texture_list=HashSet::new();
-	for path in paths{
-		let file=match std::fs::File::open(path.as_path()){
-			Ok(file)=>file,
-			Err(e)=>{
-				println!("file error {e}");
-				continue;
-			}
-		};
-		let mut input=std::io::BufReader::new(file);
-		match load_dom(&mut input){
-			Ok(dom)=>{
-				for object in dom.into_raw().1.into_values(){
-					match object.class.as_str(){
-						"Beam"=>accumulate_content_id(&mut texture_list,&object,"Texture"),
-						"Decal"=>accumulate_content_id(&mut texture_list,&object,"Texture"),
-						"Texture"=>accumulate_content_id(&mut texture_list,&object,"Texture"),
-						"FileMesh"=>accumulate_content_id(&mut texture_list,&object,"TextureId"),
-						"MeshPart"=>accumulate_content_id(&mut texture_list,&object,"TextureID"),
-						"ParticleEmitter"=>accumulate_content_id(&mut texture_list,&object,"Texture"),
-						"Sky"=>{
-							accumulate_content_id(&mut texture_list,&object,"MoonTextureId");
-							accumulate_content_id(&mut texture_list,&object,"SkyboxBk");
-							accumulate_content_id(&mut texture_list,&object,"SkyboxDn");
-							accumulate_content_id(&mut texture_list,&object,"SkyboxFt");
-							accumulate_content_id(&mut texture_list,&object,"SkyboxLf");
-							accumulate_content_id(&mut texture_list,&object,"SkyboxRt");
-							accumulate_content_id(&mut texture_list,&object,"SkyboxUp");
-							accumulate_content_id(&mut texture_list,&object,"SunTextureId");
-						},
-						_=>(),
-					}
-				}
-			},
-			Err(e)=>println!("error loading map {:?}: {:?}",path.file_name(),e),
-		}
-	}
-	let texture_list_string=texture_list.into_iter().map(|id|id.to_string()).collect::<Vec<String>>();
-	println!("Texture list:{:?}",texture_list_string.join(" "));
-	std::fs::create_dir_all("textures/unprocessed")?;
-	let output=std::process::Command::new("asset-tool")
-	.args(["download","--cookie-literal","","--output-folder","textures/unprocessed/"])
-	.args(texture_list_string)
-	.spawn()?
-	.wait_with_output()?;
-	println!("Asset tool exit_success:{}",output.status.success());
-	Ok(())
+fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
+	let mut file=std::fs::File::open(path)?;
+	let mut data=Vec::new();
+	file.read_to_end(&mut data)?;
+	Ok(Cursor::new(data))
 }
-fn download_meshes(paths:Vec<PathBuf>)->AResult<()>{
-	println!("Reading files, this could take a hot minute...");
-	let mut mesh_list=HashSet::new();
-	for path in paths{
-		let file=match std::fs::File::open(path.as_path()){
-			Ok(file)=>file,
-			Err(e)=>{
-				println!("file error {e}");
-				continue;
-			}
-		};
-		let mut input=std::io::BufReader::new(file);
-		match load_dom(&mut input){
-			Ok(dom)=>{
-				for object in dom.into_raw().1.into_values(){
-					match object.class.as_str(){
-						"MeshPart"=>accumulate_content_id(&mut mesh_list,&object,"MeshId"),
-						"SpecialMesh"=>accumulate_content_id(&mut mesh_list,&object,"MeshId"),
-						_=>(),
-					}
-				}
+#[derive(Default)]
+struct UniqueAssets{
+	meshes:HashSet<RobloxAssetId>,
+	unions:HashSet<RobloxAssetId>,
+	textures:HashSet<RobloxAssetId>,
+}
+impl UniqueAssets{
+	fn collect(&mut self,object:&Instance){
+		match object.class.as_str(){
+			"Beam"=>accumulate_content_id(&mut self.textures,object,"Texture"),
+			"Decal"=>accumulate_content_id(&mut self.textures,object,"Texture"),
+			"Texture"=>accumulate_content_id(&mut self.textures,object,"Texture"),
+			"FileMesh"=>accumulate_content_id(&mut self.textures,object,"TextureId"),
+			"MeshPart"=>{
+				accumulate_content_id(&mut self.textures,object,"TextureID");
+				accumulate_content_id(&mut self.meshes,object,"MeshId");
 			},
-			Err(e)=>println!("error loading map {:?}: {:?}",path.file_name(),e),
+			"SpecialMesh"=>accumulate_content_id(&mut self.meshes,object,"MeshId"),
+			"ParticleEmitter"=>accumulate_content_id(&mut self.textures,object,"Texture"),
+			"Sky"=>{
+				accumulate_content_id(&mut self.textures,object,"MoonTextureId");
+				accumulate_content_id(&mut self.textures,object,"SkyboxBk");
+				accumulate_content_id(&mut self.textures,object,"SkyboxDn");
+				accumulate_content_id(&mut self.textures,object,"SkyboxFt");
+				accumulate_content_id(&mut self.textures,object,"SkyboxLf");
+				accumulate_content_id(&mut self.textures,object,"SkyboxRt");
+				accumulate_content_id(&mut self.textures,object,"SkyboxUp");
+				accumulate_content_id(&mut self.textures,object,"SunTextureId");
+			},
+			"UnionOperation"=>accumulate_content_id(&mut self.unions,object,"AssetId"),
+			_=>(),
 		}
 	}
-	let mesh_list_string=mesh_list.into_iter().map(|id|id.to_string()).collect::<Vec<String>>();
-	println!("Mesh list:{:?}",mesh_list_string.join(" "));
-	std::fs::create_dir_all("meshes/")?;
-	let output=std::process::Command::new("asset-tool")
-	.args(["download","--cookie-literal","","--output-folder","meshes/"])
-	.args(mesh_list_string)
-	.spawn()?
-	.wait_with_output()?;
-	println!("Asset tool exit_success:{}",output.status.success());
+}
+fn unique_assets(path:&Path)->AResult<UniqueAssets>{
+	// read entire file
+	let mut assets=UniqueAssets::default();
+	let data=read_entire_file(path)?;
+	let dom=load_dom(data)?;
+	for object in dom.into_raw().1.into_values(){
+		assets.collect(&object);
+	}
+	Ok(assets)
+}
+struct UniqueAssetsResult{
+	path:std::path::PathBuf,
+	result:AResult<UniqueAssets>,
+}
+fn do_thread(path:std::path::PathBuf,send:std::sync::mpsc::Sender<UniqueAssetsResult>){
+	std::thread::spawn(move ||{
+		let result=unique_assets(path.as_path());
+		send.send(UniqueAssetsResult{
+			path,
+			result,
+		}).unwrap();
+	});
+}
+fn download_assets(paths:Vec<PathBuf>)->AResult<()>{
 	Ok(())
 }