diff --git a/src/main.rs b/src/main.rs
index 9ba9453..0d3d320 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,6 +37,7 @@ struct Cli{
 
 #[derive(Subcommand)]
 enum Commands{
+	DownloadHistory,
 	Download,
 	Upload,
 	Compile,
@@ -48,6 +49,24 @@ struct PathBufList{
 	paths:Vec<std::path::PathBuf>
 }
 
+#[derive(serde::Deserialize)]
+struct VersionPage{
+	previousPageCursor:Option<String>,
+	nextPageCursor:Option<String>,
+	data:Vec<AssetVersion>,
+}
+#[derive(serde::Deserialize,serde::Serialize)]
+struct AssetVersion{
+	Id:u64,
+	assetId:AssetID,
+	assetVersionNumber:u64,
+	creatorType:String,
+	creatorTargetId:u64,
+	creatingUniverseId:Option<u64>,
+	created:chrono::DateTime<chrono::Utc>,
+	isPublished:bool,
+}
+
 #[tokio::main]
 async fn main()->AResult<()>{
 	let cli=Cli::parse();
@@ -71,6 +90,7 @@ async fn main()->AResult<()>{
 	};
 
 	match cli.command{
+		Commands::DownloadHistory=>download_history(cookie.unwrap(),cli.asset_id.unwrap()).await,
 		Commands::Download=>download_list(cookie.unwrap(),vec![(cli.asset_id.unwrap(),cli.output.unwrap())]).await,
 		Commands::Upload=>upload_list(cookie.unwrap(),cli.group,vec![(cli.asset_id.unwrap(),cli.output.unwrap())]).await,
 		Commands::Compile=>compile(cli.input.unwrap(),cli.output.unwrap()),
@@ -196,6 +216,103 @@ async fn download_list(cookie:String,asset_id_file_map:AssetIDFileMap)->AResult<
 	Ok(())
 }
 
+async fn download_history(cookie:String,asset_id:AssetID)->AResult<()>{
+	let client=reqwest::Client::new();
+	let asset_id_string=asset_id.to_string();
+
+	let mut cursor:Option<String>=None;
+	let mut asset_list=Vec::new();
+	loop{
+		let mut url=reqwest::Url::parse(format!("https://develop.roblox.com/v1/assets/{}/saved-versions",asset_id).as_str())?;
+		//url borrow scope
+		{
+			let mut query=url.query_pairs_mut();//borrow here
+			//query.append_pair("sortOrder","Asc");
+			//query.append_pair("limit","100");
+			//query.append_pair("count","100");
+			match &cursor{
+				Some(next_page)=>{query.append_pair("cursor",next_page);}
+				None=>(),
+			}
+		}
+		println!("url={}",url);
+		let resp=client.get(url)
+		.header("Cookie",cookie.clone())
+		.send().await?;
+		println!("resp:{:?}",resp);
+		match resp.json::<VersionPage>().await{
+			Ok(mut page)=>{
+				asset_list.append(&mut page.data);
+				if page.nextPageCursor.is_none(){
+					break;
+				}
+				cursor=page.nextPageCursor;
+			},
+			Err(e)=>panic!("error: {}",e),
+		}
+	}
+	asset_list.sort_by(|a,b|a.assetVersionNumber.cmp(&b.assetVersionNumber));
+	let mut path=std::path::PathBuf::new();
+	path.set_file_name("versions.json");
+	tokio::fs::write(path,serde_json::to_string(&asset_list)?).await?;
+
+	//download all versions
+	futures::stream::iter(asset_list)
+	.map(|asset_version|{
+		let client=&client;
+		let cookie=cookie.as_str();
+		let asset_id_str=asset_id_string.as_str();
+		async move{
+			let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v1/asset/")?;
+			//url borrow scope
+			{
+				let mut query=url.query_pairs_mut();//borrow here
+				query.append_pair("ID",asset_id_str);
+				query.append_pair("version",asset_version.assetVersionNumber.to_string().as_str());
+			}
+			println!("url={}",url);
+			let mut result=Err(anyhow::Error::msg("all requests failed"));
+			for i in 1..=8{
+				let resp=client.get(url.clone())
+				.header("Cookie",cookie)
+				.send().await?;
+
+				if !resp.status().is_success(){
+					println!("request {} failed",i);
+					continue;
+				}
+
+				let mut path=std::path::PathBuf::new();
+				path.set_file_name(format!("{}_v{}.rbxl",asset_id,asset_version.assetVersionNumber));
+				result=Ok((path,resp.bytes().await?));
+				break;
+			}
+			result
+		}
+	})
+	.buffer_unordered(CONCURRENT_REQUESTS)
+	.for_each(|b:AResult<_>|async{
+			match b{
+				Ok((dest,body))=>{
+					let contents=match maybe_gzip_decode(&mut std::io::Cursor::new(body)){
+						Ok(ReaderType::GZip(readable))=>read_readable(readable),
+						Ok(ReaderType::Raw(readable))=>read_readable(readable),
+						Err(e)=>Err(e),
+					};
+					match contents{
+						Ok(data)=>match tokio::fs::write(dest,data).await{
+							Err(e)=>eprintln!("fs error: {}",e),
+							_=>(),
+						},
+						Err(e)=>eprintln!("gzip error: {}",e),
+					};
+				},
+				Err(e)=>eprintln!("dl error: {}",e),
+			}
+		}).await;
+	Ok(())
+}
+
 fn load_dom<R:Read+Seek>(input:&mut 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)){