From e40041a894676077988153cda95074892b422b2c Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Tue, 8 Apr 2025 16:07:14 -0700 Subject: [PATCH 1/8] rbx_asset: change api for asset location again --- rbx_asset/src/cloud.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/rbx_asset/src/cloud.rs b/rbx_asset/src/cloud.rs index f65c7aa..0016b97 100644 --- a/rbx_asset/src/cloud.rs +++ b/rbx_asset/src/cloud.rs @@ -178,22 +178,28 @@ impl std::fmt::Display for GetError{ } impl std::error::Error for GetError{} +#[derive(Debug,serde::Deserialize,serde::Serialize)] +pub struct AssetLocation( + // the location is private so users cannot mutate it + String +); +impl AssetLocation{ + pub fn location(&self)->&str{ + let Self(location)=self; + location + } +} + #[derive(Debug,serde::Deserialize)] #[allow(nonstandard_style,dead_code)] -pub struct AssetLocation{ - // this field is private so users cannot mutate it - location:String, +pub struct AssetLocationInfo{ + pub location:Option<AssetLocation>, pub requestId:String, pub IsHashDynamic:bool, pub IsCopyrightProtected:bool, pub isArchived:bool, pub assetTypeId:u32, } -impl AssetLocation{ - pub fn location(&self)->&str{ - &self.location - } -} pub struct AssetVersionsRequest{ pub asset_id:u64, @@ -424,7 +430,7 @@ impl Context{ ).await.map_err(GetError::Response)? .json::<AssetResponse>().await.map_err(GetError::Reqwest) } - pub async fn get_asset_location(&self,config:GetAssetLatestRequest)->Result<AssetLocation,GetError>{ + pub async fn get_asset_location(&self,config:GetAssetLatestRequest)->Result<AssetLocationInfo,GetError>{ let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}",config.asset_id); let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; @@ -433,7 +439,7 @@ impl Context{ ).await.map_err(GetError::Response)? .json().await.map_err(GetError::Reqwest) } - pub async fn get_asset_version_location(&self,config:GetAssetVersionRequest)->Result<AssetLocation,GetError>{ + pub async fn get_asset_version_location(&self,config:GetAssetVersionRequest)->Result<AssetLocationInfo,GetError>{ let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}/version/{}",config.asset_id,config.version); let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; @@ -443,7 +449,7 @@ impl Context{ .json().await.map_err(GetError::Reqwest) } pub async fn get_asset(&self,config:&AssetLocation)->Result<Vec<u8>,GetError>{ - let url=reqwest::Url::parse(config.location.as_str()).map_err(GetError::ParseError)?; + let url=reqwest::Url::parse(config.location()).map_err(GetError::ParseError)?; let body=crate::response_ok( self.get(url).await.map_err(GetError::Reqwest)? From df2a5bb9ce49c78d9fb5ad48ebeac425266ffc1a Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Tue, 8 Apr 2025 16:07:33 -0700 Subject: [PATCH 2/8] rbx_asset: v0.4.3 optional AssetLocation --- Cargo.lock | 2 +- rbx_asset/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f60a23..a09533c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1293,7 +1293,7 @@ dependencies = [ [[package]] name = "rbx_asset" -version = "0.4.2" +version = "0.4.3" dependencies = [ "chrono", "flate2", diff --git a/rbx_asset/Cargo.toml b/rbx_asset/Cargo.toml index e652476..27c955b 100644 --- a/rbx_asset/Cargo.toml +++ b/rbx_asset/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rbx_asset" -version = "0.4.2" +version = "0.4.3" edition = "2021" publish = ["strafesnet"] repository = "https://git.itzana.me/StrafesNET/asset-tool" From 50145460b9bb9ec8e0cf7a1dc0c0d48cdb9f7efd Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Thu, 10 Apr 2025 00:24:55 -0700 Subject: [PATCH 3/8] rbx_asset: simplify gzip logic --- Cargo.lock | 1 + rbx_asset/Cargo.toml | 1 + rbx_asset/src/cloud.rs | 8 ++------ rbx_asset/src/cookie.rs | 15 ++++----------- rbx_asset/src/lib.rs | 25 +++++++++---------------- 5 files changed, 17 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a09533c..c020c03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1295,6 +1295,7 @@ dependencies = [ name = "rbx_asset" version = "0.4.3" dependencies = [ + "bytes", "chrono", "flate2", "reqwest", diff --git a/rbx_asset/Cargo.toml b/rbx_asset/Cargo.toml index 27c955b..2b0ad78 100644 --- a/rbx_asset/Cargo.toml +++ b/rbx_asset/Cargo.toml @@ -11,6 +11,7 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bytes = "1.10.1" chrono = { version = "0.4.38", features = ["serde"] } flate2 = "1.0.29" reqwest = { version = "0.12.4", features = ["json","multipart"] } diff --git a/rbx_asset/src/cloud.rs b/rbx_asset/src/cloud.rs index 0016b97..fae4f61 100644 --- a/rbx_asset/src/cloud.rs +++ b/rbx_asset/src/cloud.rs @@ -1,4 +1,4 @@ -use crate::{ResponseError,ReaderType,maybe_gzip_decode,read_readable}; +use crate::{ResponseError,maybe_gzip_decode}; #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] @@ -456,11 +456,7 @@ impl Context{ ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; - match maybe_gzip_decode(std::io::Cursor::new(body)){ - Ok(ReaderType::GZip(readable))=>read_readable(readable), - Ok(ReaderType::Raw(readable))=>read_readable(readable), - Err(e)=>Err(e), - }.map_err(GetError::IO) + maybe_gzip_decode(body).map_err(GetError::IO) } pub async fn get_asset_versions(&self,config:AssetVersionsRequest)->Result<AssetVersionsResponse,AssetVersionsError>{ let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions",config.asset_id); diff --git a/rbx_asset/src/cookie.rs b/rbx_asset/src/cookie.rs index 7ad53f2..5ba4532 100644 --- a/rbx_asset/src/cookie.rs +++ b/rbx_asset/src/cookie.rs @@ -1,4 +1,4 @@ -use crate::{ResponseError,ReaderType,maybe_gzip_decode,read_readable}; +use crate::{ResponseError,maybe_gzip_decode}; #[derive(Debug)] pub enum PostError{ @@ -464,11 +464,8 @@ impl Context{ ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; - match maybe_gzip_decode(std::io::Cursor::new(body)){ - Ok(ReaderType::GZip(readable))=>read_readable(readable), - Ok(ReaderType::Raw(readable))=>read_readable(readable), - Err(e)=>Err(e), - }.map_err(GetError::IO) + + maybe_gzip_decode(body).map_err(GetError::IO) } pub async fn get_asset_v2(&self,config:GetAssetRequest)->Result<GetAssetV2,GetAssetV2Error>{ let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v2/asset").map_err(GetAssetV2Error::ParseError)?; @@ -508,11 +505,7 @@ impl Context{ ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; - match maybe_gzip_decode(std::io::Cursor::new(body)){ - Ok(ReaderType::GZip(readable))=>read_readable(readable), - Ok(ReaderType::Raw(readable))=>read_readable(readable), - Err(e)=>Err(e), - }.map_err(GetError::IO) + maybe_gzip_decode(body).map_err(GetError::IO) } pub async fn get_asset_details(&self,config:GetAssetDetailsRequest)->Result<AssetDetails,GetError>{ let url=reqwest::Url::parse(format!("https://economy.roblox.com/v2/assets/{}/details",config.asset_id).as_str()).map_err(GetError::ParseError)?; diff --git a/rbx_asset/src/lib.rs b/rbx_asset/src/lib.rs index c41ff74..0c6f8fb 100644 --- a/rbx_asset/src/lib.rs +++ b/rbx_asset/src/lib.rs @@ -36,21 +36,14 @@ pub(crate) async fn response_ok(response:reqwest::Response)->Result<reqwest::Res } } -//idk how to do this better -pub(crate) enum ReaderType<R:std::io::Read>{ - GZip(flate2::read::GzDecoder<std::io::BufReader<R>>), - Raw(std::io::BufReader<R>), -} -pub(crate) fn maybe_gzip_decode<R:std::io::Read>(input:R)->std::io::Result<ReaderType<R>>{ - let mut buf=std::io::BufReader::new(input); - let peek=std::io::BufRead::fill_buf(&mut buf)?; - match &peek[0..2]{ - b"\x1f\x8b"=>Ok(ReaderType::GZip(flate2::read::GzDecoder::new(buf))), - _=>Ok(ReaderType::Raw(buf)), +pub(crate) fn maybe_gzip_decode(data:bytes::Bytes)->std::io::Result<Vec<u8>>{ + match data.get(0..2){ + Some(b"\x1f\x8b")=>{ + use std::io::Read; + let mut buf=Vec::new(); + flate2::read::GzDecoder::new(std::io::Cursor::new(data)).read_to_end(&mut buf)?; + Ok(buf) + }, + _=>Ok(data.to_vec()), } } -pub(crate) fn read_readable(mut readable:impl std::io::Read)->std::io::Result<Vec<u8>>{ - let mut contents=Vec::new(); - readable.read_to_end(&mut contents)?; - Ok(contents) -} From d77312309f53966ff8c90d9a460354db51e915ce Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Thu, 10 Apr 2025 15:26:02 -0700 Subject: [PATCH 4/8] rbx_asset: helpers for integers within a string --- rbx_asset/src/cloud.rs | 14 ++++++++++---- rbx_asset/src/lib.rs | 1 + rbx_asset/src/util.rs | 21 +++++++++++++++++++++ src/main.rs | 10 +++++----- 4 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 rbx_asset/src/util.rs diff --git a/rbx_asset/src/cloud.rs b/rbx_asset/src/cloud.rs index fae4f61..bdc5fd1 100644 --- a/rbx_asset/src/cloud.rs +++ b/rbx_asset/src/cloud.rs @@ -1,3 +1,4 @@ +use crate::util::{serialize_u64,deserialize_u64}; use crate::{ResponseError,maybe_gzip_decode}; #[derive(Debug,serde::Deserialize,serde::Serialize)] @@ -65,8 +66,8 @@ pub struct UpdateAssetRequest{ #[derive(Clone,Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] pub enum Creator{ - userId(String),//u64 string - groupId(String),//u64 string + userId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64), + groupId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64), } #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] @@ -146,14 +147,19 @@ pub struct GetAssetLatestRequest{ #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] pub struct AssetResponse{ - pub assetId:String,//u64 wrapped in quotes wohoo!! + //u64 wrapped in quotes wohoo!! + #[serde(deserialize_with="deserialize_u64")] + #[serde(serialize_with="serialize_u64")] + pub assetId:u64, pub assetType:AssetType, pub creationContext:CreationContext, pub description:Option<String>, pub displayName:String, pub path:String, pub revisionCreateTime:chrono::DateTime<chrono::Utc>, - pub revisionId:String,//u64 + #[serde(deserialize_with="deserialize_u64")] + #[serde(serialize_with="serialize_u64")] + pub revisionId:u64, pub moderationResult:ModerationResult, pub icon:Option<String>, #[serde(default)] diff --git a/rbx_asset/src/lib.rs b/rbx_asset/src/lib.rs index 0c6f8fb..7a5274a 100644 --- a/rbx_asset/src/lib.rs +++ b/rbx_asset/src/lib.rs @@ -1,5 +1,6 @@ pub mod cloud; pub mod cookie; +mod util; #[allow(dead_code)] #[derive(Debug)] diff --git a/rbx_asset/src/util.rs b/rbx_asset/src/util.rs new file mode 100644 index 0000000..24ae3d5 --- /dev/null +++ b/rbx_asset/src/util.rs @@ -0,0 +1,21 @@ +use serde::de::{Error,Unexpected}; +use serde::{Deserializer,Serializer}; + +struct U64StringVisitor; +impl serde::de::Visitor<'_> for U64StringVisitor{ + type Value=u64; + fn expecting(&self,formatter:&mut std::fmt::Formatter)->std::fmt::Result{ + write!(formatter,"string value with int") + } + fn visit_str<E:Error>(self,v:&str)->Result<Self::Value,E>{ + v.parse().map_err(|_|E::invalid_value(Unexpected::Str(v),&"u64")) + } +} + +pub(crate) fn deserialize_u64<'de,D:Deserializer<'de>>(deserializer:D)->Result<u64,D::Error>{ + deserializer.deserialize_any(U64StringVisitor) +} + +pub(crate) fn serialize_u64<S:Serializer>(v:&u64,serializer:S)->Result<S::Ok,S::Error>{ + serializer.serialize_str(v.to_string().as_str()) +} diff --git a/src/main.rs b/src/main.rs index ba89009..b30aa28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -563,8 +563,8 @@ async fn main()->AResult<()>{ subcommand.api_key_file, ).await?, creator:match (subcommand.creator_user_id,subcommand.creator_group_id){ - (Some(user_id),None)=>rbx_asset::cloud::Creator::userId(user_id.to_string()), - (None,Some(group_id))=>rbx_asset::cloud::Creator::groupId(group_id.to_string()), + (Some(user_id),None)=>rbx_asset::cloud::Creator::userId(user_id), + (None,Some(group_id))=>rbx_asset::cloud::Creator::groupId(group_id), other=>Err(anyhow!("Invalid creator {other:?}"))?, }, input_file:subcommand.input_file, @@ -585,8 +585,8 @@ async fn main()->AResult<()>{ subcommand.cookie_file, ).await?, creator:match (subcommand.creator_user_id,subcommand.creator_group_id){ - (Some(user_id),None)=>rbx_asset::cloud::Creator::userId(user_id.to_string()), - (None,Some(group_id))=>rbx_asset::cloud::Creator::groupId(group_id.to_string()), + (Some(user_id),None)=>rbx_asset::cloud::Creator::userId(user_id), + (None,Some(group_id))=>rbx_asset::cloud::Creator::groupId(group_id), other=>Err(anyhow!("Invalid creator {other:?}"))?, }, description:subcommand.description.unwrap_or_else(||String::with_capacity(0)), @@ -904,7 +904,7 @@ async fn create_asset_medias(config:CreateAssetMediasConfig)->AResult<()>{ async move{ let asset_response=asset_response_result.map_err(DownloadDecalError::PollOperation)?; let file=cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ - asset_id:asset_response.assetId.parse().map_err(DownloadDecalError::ParseInt)?, + asset_id:asset_response.assetId, version:None, }).await.map_err(DownloadDecalError::Get)?; let dom=load_dom(std::io::Cursor::new(file)).map_err(DownloadDecalError::LoadDom)?; From 041cc75015b7af11712931c0ddac920a2e162b28 Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Thu, 10 Apr 2025 17:24:44 -0700 Subject: [PATCH 5/8] rbx_asset: move code into util, types --- rbx_asset/src/cloud.rs | 26 +++++++++++----------- rbx_asset/src/cookie.rs | 21 +++++++++--------- rbx_asset/src/lib.rs | 48 +---------------------------------------- rbx_asset/src/types.rs | 18 ++++++++++++++++ rbx_asset/src/util.rs | 31 ++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 70 deletions(-) create mode 100644 rbx_asset/src/types.rs diff --git a/rbx_asset/src/cloud.rs b/rbx_asset/src/cloud.rs index bdc5fd1..93085d6 100644 --- a/rbx_asset/src/cloud.rs +++ b/rbx_asset/src/cloud.rs @@ -1,5 +1,5 @@ -use crate::util::{serialize_u64,deserialize_u64}; -use crate::{ResponseError,maybe_gzip_decode}; +use crate::util::{serialize_u64,deserialize_u64,response_ok,maybe_gzip_decode}; +use crate::types::{ResponseError}; #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] @@ -381,7 +381,7 @@ impl Context{ .text("request",request_config) .part("fileContent",part); - let operation=crate::response_ok( + let operation=response_ok( self.post_form(url,form).await.map_err(CreateError::Reqwest)? ).await.map_err(CreateError::Response)? .json::<RobloxOperation>().await.map_err(CreateError::Reqwest)?; @@ -400,7 +400,7 @@ impl Context{ .text("request",request_config) .part("fileContent",reqwest::multipart::Part::bytes(body)); - let operation=crate::response_ok( + let operation=response_ok( self.patch_form(url,form).await.map_err(UpdateError::Reqwest)? ).await.map_err(UpdateError::Response)? .json::<RobloxOperation>().await.map_err(UpdateError::Reqwest)?; @@ -413,7 +413,7 @@ impl Context{ let raw_url=format!("https://apis.roblox.com/assets/v1/operations/{}",config.operation_id); let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; - crate::response_ok( + response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .json::<RobloxOperation>().await.map_err(GetError::Reqwest) @@ -422,7 +422,7 @@ impl Context{ let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}",config.asset_id); let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; - crate::response_ok( + response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .json::<AssetResponse>().await.map_err(GetError::Reqwest) @@ -431,7 +431,7 @@ impl Context{ let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions/{}",config.asset_id,config.version); let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; - crate::response_ok( + response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .json::<AssetResponse>().await.map_err(GetError::Reqwest) @@ -440,7 +440,7 @@ impl Context{ let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}",config.asset_id); let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; - crate::response_ok( + response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .json().await.map_err(GetError::Reqwest) @@ -449,7 +449,7 @@ impl Context{ let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}/version/{}",config.asset_id,config.version); let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?; - crate::response_ok( + response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .json().await.map_err(GetError::Reqwest) @@ -457,7 +457,7 @@ impl Context{ pub async fn get_asset(&self,config:&AssetLocation)->Result<Vec<u8>,GetError>{ let url=reqwest::Url::parse(config.location()).map_err(GetError::ParseError)?; - let body=crate::response_ok( + let body=response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; @@ -468,7 +468,7 @@ impl Context{ let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions",config.asset_id); let url=reqwest::Url::parse(raw_url.as_str()).map_err(AssetVersionsError::ParseError)?; - crate::response_ok( + response_ok( self.get(url).await.map_err(AssetVersionsError::Reqwest)? ).await.map_err(AssetVersionsError::Response)? .json::<AssetVersionsResponse>().await.map_err(AssetVersionsError::Reqwest) @@ -483,7 +483,7 @@ impl Context{ } } - crate::response_ok( + response_ok( self.get(url).await.map_err(InventoryPageError::Reqwest)? ).await.map_err(InventoryPageError::Response)? .json::<InventoryPageResponse>().await.map_err(InventoryPageError::Reqwest) @@ -497,7 +497,7 @@ impl Context{ query.append_pair("versionType","Published"); } - crate::response_ok( + response_ok( self.post(url,body).await.map_err(UpdateError::Reqwest)? ).await.map_err(UpdateError::Response)? .json::<UpdatePlaceResponse>().await.map_err(UpdateError::Reqwest) diff --git a/rbx_asset/src/cookie.rs b/rbx_asset/src/cookie.rs index 5ba4532..8369965 100644 --- a/rbx_asset/src/cookie.rs +++ b/rbx_asset/src/cookie.rs @@ -1,4 +1,5 @@ -use crate::{ResponseError,maybe_gzip_decode}; +use crate::util::{response_ok,maybe_gzip_decode}; +use crate::types::ResponseError; #[derive(Debug)] pub enum PostError{ @@ -371,7 +372,7 @@ impl Context{ query.append_pair("groupId",group_id.to_string().as_str()); } } - let response=crate::response_ok( + let response=response_ok( self.post(url,body).await.map_err(CreateError::PostError)? ).await.map_err(CreateError::Response)?; @@ -423,7 +424,7 @@ impl Context{ query.append_pair("groupId",group_id.to_string().as_str()); } } - let response=crate::response_ok( + let response=response_ok( self.post(url,body).await.map_err(UploadError::PostError)? ).await.map_err(UploadError::Response)?; @@ -459,7 +460,7 @@ impl Context{ query.append_pair("version",version.to_string().as_str()); } } - let body=crate::response_ok( + let body=response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; @@ -477,7 +478,7 @@ impl Context{ query.append_pair("version",version.to_string().as_str()); } } - let response=crate::response_ok( + let response=response_ok( self.get(url).await.map_err(GetAssetV2Error::Reqwest)? ).await.map_err(GetAssetV2Error::Response)?; @@ -500,7 +501,7 @@ impl Context{ pub async fn get_asset_v2_download(&self,config:&GetAssetV2Location)->Result<Vec<u8>,GetError>{ let url=reqwest::Url::parse(config.location.as_str()).map_err(GetError::ParseError)?; - let body=crate::response_ok( + let body=response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; @@ -509,7 +510,7 @@ impl Context{ } pub async fn get_asset_details(&self,config:GetAssetDetailsRequest)->Result<AssetDetails,GetError>{ let url=reqwest::Url::parse(format!("https://economy.roblox.com/v2/assets/{}/details",config.asset_id).as_str()).map_err(GetError::ParseError)?; - crate::response_ok( + response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .json().await.map_err(GetError::Reqwest) @@ -526,7 +527,7 @@ impl Context{ query.append_pair("cursor",cursor); } } - crate::response_ok( + response_ok( self.get(url).await.map_err(PageError::Reqwest)? ).await.map_err(PageError::Response)? .json::<AssetVersionsPageResponse>().await.map_err(PageError::Reqwest) @@ -541,7 +542,7 @@ impl Context{ query.append_pair("cursor",cursor); } } - crate::response_ok( + response_ok( self.get(url).await.map_err(PageError::Reqwest)? ).await.map_err(PageError::Response)? .json::<CreationsPageResponse>().await.map_err(PageError::Reqwest) @@ -555,7 +556,7 @@ impl Context{ query.append_pair("cursor",cursor); } } - crate::response_ok( + response_ok( self.get(url).await.map_err(PageError::Reqwest)? ).await.map_err(PageError::Response)? .json::<UserInventoryPageResponse>().await.map_err(PageError::Reqwest) diff --git a/rbx_asset/src/lib.rs b/rbx_asset/src/lib.rs index 7a5274a..a6a3487 100644 --- a/rbx_asset/src/lib.rs +++ b/rbx_asset/src/lib.rs @@ -1,50 +1,4 @@ pub mod cloud; pub mod cookie; +pub mod types; mod util; - -#[allow(dead_code)] -#[derive(Debug)] -pub struct StatusCodeWithUrlAndBody{ - pub status_code:reqwest::StatusCode, - pub url:url::Url, - pub body:String, -} -#[derive(Debug)] -pub enum ResponseError{ - Reqwest(reqwest::Error), - StatusCodeWithUrlAndBody(StatusCodeWithUrlAndBody), -} -impl std::fmt::Display for ResponseError{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f,"{self:?}") - } -} -impl std::error::Error for ResponseError{} -// lazy function to draw out meaningful info from http response on failure -pub(crate) async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,ResponseError>{ - let status_code=response.status(); - if status_code.is_success(){ - Ok(response) - }else{ - let url=response.url().to_owned(); - let bytes=response.bytes().await.map_err(ResponseError::Reqwest)?; - let body=String::from_utf8_lossy(&bytes).to_string(); - Err(ResponseError::StatusCodeWithUrlAndBody(StatusCodeWithUrlAndBody{ - status_code, - url, - body, - })) - } -} - -pub(crate) fn maybe_gzip_decode(data:bytes::Bytes)->std::io::Result<Vec<u8>>{ - match data.get(0..2){ - Some(b"\x1f\x8b")=>{ - use std::io::Read; - let mut buf=Vec::new(); - flate2::read::GzDecoder::new(std::io::Cursor::new(data)).read_to_end(&mut buf)?; - Ok(buf) - }, - _=>Ok(data.to_vec()), - } -} diff --git a/rbx_asset/src/types.rs b/rbx_asset/src/types.rs new file mode 100644 index 0000000..b9ef790 --- /dev/null +++ b/rbx_asset/src/types.rs @@ -0,0 +1,18 @@ +#[allow(dead_code)] +#[derive(Debug)] +pub struct StatusCodeWithUrlAndBody{ + pub status_code:reqwest::StatusCode, + pub url:url::Url, + pub body:String, +} +#[derive(Debug)] +pub enum ResponseError{ + Reqwest(reqwest::Error), + StatusCodeWithUrlAndBody(StatusCodeWithUrlAndBody), +} +impl std::fmt::Display for ResponseError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f,"{self:?}") + } +} +impl std::error::Error for ResponseError{} diff --git a/rbx_asset/src/util.rs b/rbx_asset/src/util.rs index 24ae3d5..5cc08b7 100644 --- a/rbx_asset/src/util.rs +++ b/rbx_asset/src/util.rs @@ -1,3 +1,34 @@ +use crate::types::{ResponseError,StatusCodeWithUrlAndBody}; + +// lazy function to draw out meaningful info from http response on failure +pub(crate) async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,ResponseError>{ + let status_code=response.status(); + if status_code.is_success(){ + Ok(response) + }else{ + let url=response.url().to_owned(); + let bytes=response.bytes().await.map_err(ResponseError::Reqwest)?; + let body=String::from_utf8_lossy(&bytes).to_string(); + Err(ResponseError::StatusCodeWithUrlAndBody(StatusCodeWithUrlAndBody{ + status_code, + url, + body, + })) + } +} + +pub(crate) fn maybe_gzip_decode(data:bytes::Bytes)->std::io::Result<Vec<u8>>{ + match data.get(0..2){ + Some(b"\x1f\x8b")=>{ + use std::io::Read; + let mut buf=Vec::new(); + flate2::read::GzDecoder::new(std::io::Cursor::new(data)).read_to_end(&mut buf)?; + Ok(buf) + }, + _=>Ok(data.to_vec()), + } +} + use serde::de::{Error,Unexpected}; use serde::{Deserializer,Serializer}; From 31aae80cc50431d669c21df351881d442d36a18a Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Thu, 10 Apr 2025 15:55:09 -0700 Subject: [PATCH 6/8] rbx_asset: change api to save intermediate allocation --- rbx_asset/Cargo.toml | 6 ++++- rbx_asset/src/cloud.rs | 11 +++++---- rbx_asset/src/cookie.rs | 19 ++++++++-------- rbx_asset/src/types.rs | 50 +++++++++++++++++++++++++++++++++++++++++ rbx_asset/src/util.rs | 12 ---------- src/main.rs | 26 ++++++++++----------- 6 files changed, 82 insertions(+), 42 deletions(-) diff --git a/rbx_asset/Cargo.toml b/rbx_asset/Cargo.toml index 2b0ad78..0120817 100644 --- a/rbx_asset/Cargo.toml +++ b/rbx_asset/Cargo.toml @@ -10,10 +10,14 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["gzip"] +gzip = ["dep:flate2"] + [dependencies] bytes = "1.10.1" chrono = { version = "0.4.38", features = ["serde"] } -flate2 = "1.0.29" +flate2 = { version = "1.0.29", optional = true } reqwest = { version = "0.12.4", features = ["json","multipart"] } serde = { version = "1.0.199", features = ["derive"] } serde_json = "1.0.111" diff --git a/rbx_asset/src/cloud.rs b/rbx_asset/src/cloud.rs index 93085d6..71f4a9e 100644 --- a/rbx_asset/src/cloud.rs +++ b/rbx_asset/src/cloud.rs @@ -1,5 +1,5 @@ -use crate::util::{serialize_u64,deserialize_u64,response_ok,maybe_gzip_decode}; -use crate::types::{ResponseError}; +use crate::util::{serialize_u64,deserialize_u64,response_ok}; +use crate::types::{ResponseError,MaybeGzippedBytes}; #[derive(Debug,serde::Deserialize,serde::Serialize)] #[allow(nonstandard_style,dead_code)] @@ -175,7 +175,6 @@ pub enum GetError{ ParseError(url::ParseError), Response(ResponseError), Reqwest(reqwest::Error), - IO(std::io::Error) } impl std::fmt::Display for GetError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ @@ -454,15 +453,15 @@ impl Context{ ).await.map_err(GetError::Response)? .json().await.map_err(GetError::Reqwest) } - pub async fn get_asset(&self,config:&AssetLocation)->Result<Vec<u8>,GetError>{ + pub async fn get_asset(&self,config:&AssetLocation)->Result<MaybeGzippedBytes,GetError>{ let url=reqwest::Url::parse(config.location()).map_err(GetError::ParseError)?; - let body=response_ok( + let bytes=response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; - maybe_gzip_decode(body).map_err(GetError::IO) + Ok(MaybeGzippedBytes::new(bytes)) } pub async fn get_asset_versions(&self,config:AssetVersionsRequest)->Result<AssetVersionsResponse,AssetVersionsError>{ let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions",config.asset_id); diff --git a/rbx_asset/src/cookie.rs b/rbx_asset/src/cookie.rs index 8369965..efee3dc 100644 --- a/rbx_asset/src/cookie.rs +++ b/rbx_asset/src/cookie.rs @@ -1,5 +1,5 @@ -use crate::util::{response_ok,maybe_gzip_decode}; -use crate::types::ResponseError; +use crate::util::response_ok; +use crate::types::{ResponseError,MaybeGzippedBytes}; #[derive(Debug)] pub enum PostError{ @@ -92,7 +92,6 @@ pub enum GetError{ ParseError(url::ParseError), Response(ResponseError), Reqwest(reqwest::Error), - IO(std::io::Error) } impl std::fmt::Display for GetError{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -450,7 +449,7 @@ impl Context{ }) } } - pub async fn get_asset(&self,config:GetAssetRequest)->Result<Vec<u8>,GetError>{ + pub async fn get_asset(&self,config:GetAssetRequest)->Result<MaybeGzippedBytes,GetError>{ let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v1/asset/").map_err(GetError::ParseError)?; //url borrow scope { @@ -460,13 +459,13 @@ impl Context{ query.append_pair("version",version.to_string().as_str()); } } - let body=response_ok( + + let bytes=response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; - - maybe_gzip_decode(body).map_err(GetError::IO) + Ok(MaybeGzippedBytes::new(bytes)) } pub async fn get_asset_v2(&self,config:GetAssetRequest)->Result<GetAssetV2,GetAssetV2Error>{ let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v2/asset").map_err(GetAssetV2Error::ParseError)?; @@ -498,15 +497,15 @@ impl Context{ info, }) } - pub async fn get_asset_v2_download(&self,config:&GetAssetV2Location)->Result<Vec<u8>,GetError>{ + pub async fn get_asset_v2_download(&self,config:&GetAssetV2Location)->Result<MaybeGzippedBytes,GetError>{ let url=reqwest::Url::parse(config.location.as_str()).map_err(GetError::ParseError)?; - let body=response_ok( + let bytes=response_ok( self.get(url).await.map_err(GetError::Reqwest)? ).await.map_err(GetError::Response)? .bytes().await.map_err(GetError::Reqwest)?; - maybe_gzip_decode(body).map_err(GetError::IO) + Ok(MaybeGzippedBytes::new(bytes)) } pub async fn get_asset_details(&self,config:GetAssetDetailsRequest)->Result<AssetDetails,GetError>{ let url=reqwest::Url::parse(format!("https://economy.roblox.com/v2/assets/{}/details",config.asset_id).as_str()).map_err(GetError::ParseError)?; diff --git a/rbx_asset/src/types.rs b/rbx_asset/src/types.rs index b9ef790..2aa64f5 100644 --- a/rbx_asset/src/types.rs +++ b/rbx_asset/src/types.rs @@ -16,3 +16,53 @@ impl std::fmt::Display for ResponseError{ } } impl std::error::Error for ResponseError{} + +#[cfg(feature="gzip")] +use std::io::Cursor; +#[cfg(feature="gzip")] +use flate2::read::GzDecoder; + +/// Some bytes that might be gzipped. Use the read_with or to_vec methods to transparently decode gzip. +pub struct MaybeGzippedBytes{ + bytes:bytes::Bytes, +} +impl MaybeGzippedBytes{ + pub(crate) fn new(bytes:bytes::Bytes)->Self{ + Self{bytes} + } + pub fn into_inner(self)->bytes::Bytes{ + self.bytes + } + /// get a reference to the bytes, ignoring gzip decoding + pub fn as_raw_ref(&self)->&[u8]{ + self.bytes.as_ref() + } + /// Transparently decode gzip data, if present (intermediate allocation) + #[cfg(feature="gzip")] + pub fn to_vec(&self)->std::io::Result<Vec<u8>>{ + use std::io::Read; + match self.bytes.get(0..2){ + Some(b"\x1f\x8b")=>{ + let mut buf=Vec::new(); + GzDecoder::new(Cursor::new(self.bytes.as_ref())).read_to_end(&mut buf)?; + Ok(buf) + }, + _=>Ok(self.bytes.to_vec()) + } + } + /// Read the bytes with the provided decoders. + /// The idea is to make a function that is generic over std::io::Read + /// and pass the same function to both closures. + /// This two closure hack must be done because of the different concrete types. + #[cfg(feature="gzip")] + pub fn read_with<'a,ReadGzip,ReadRaw,T>(&'a self,read_gzip:ReadGzip,read_raw:ReadRaw)->T + where + ReadGzip:Fn(GzDecoder<Cursor<&'a [u8]>>)->T, + ReadRaw:Fn(Cursor<&'a [u8]>)->T, + { + match self.bytes.get(0..2){ + Some(b"\x1f\x8b")=>read_gzip(GzDecoder::new(Cursor::new(self.bytes.as_ref()))), + _=>read_raw(Cursor::new(self.bytes.as_ref())) + } + } +} diff --git a/rbx_asset/src/util.rs b/rbx_asset/src/util.rs index 5cc08b7..9f8a785 100644 --- a/rbx_asset/src/util.rs +++ b/rbx_asset/src/util.rs @@ -17,18 +17,6 @@ pub(crate) async fn response_ok(response:reqwest::Response)->Result<reqwest::Res } } -pub(crate) fn maybe_gzip_decode(data:bytes::Bytes)->std::io::Result<Vec<u8>>{ - match data.get(0..2){ - Some(b"\x1f\x8b")=>{ - use std::io::Read; - let mut buf=Vec::new(); - flate2::read::GzDecoder::new(std::io::Cursor::new(data)).read_to_end(&mut buf)?; - Ok(buf) - }, - _=>Ok(data.to_vec()), - } -} - use serde::de::{Error,Unexpected}; use serde::{Deserializer,Serializer}; diff --git a/src/main.rs b/src/main.rs index b30aa28..e8703bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -903,11 +903,11 @@ async fn create_asset_medias(config:CreateAssetMediasConfig)->AResult<()>{ async move{(path, async move{ let asset_response=asset_response_result.map_err(DownloadDecalError::PollOperation)?; - let file=cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ + let maybe_gzip=cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ asset_id:asset_response.assetId, version:None, }).await.map_err(DownloadDecalError::Get)?; - let dom=load_dom(std::io::Cursor::new(file)).map_err(DownloadDecalError::LoadDom)?; + let dom=maybe_gzip.read_with(load_dom,load_dom).map_err(DownloadDecalError::LoadDom)?; let instance=dom.get_by_ref( *dom.root().children().first().ok_or(DownloadDecalError::NoFirstInstance)? ).ok_or(DownloadDecalError::NoFirstInstance)?; @@ -993,8 +993,8 @@ async fn asset_details(cookie:Cookie,asset_id:AssetID)->AResult<()>{ async fn download_version(cookie:Cookie,asset_id:AssetID,version:Option<u64>,dest:PathBuf)->AResult<()>{ let context=CookieContext::new(cookie); - let data=context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id,version}).await?; - tokio::fs::write(dest,data).await?; + let maybe_gzip=context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id,version}).await?; + tokio::fs::write(dest,maybe_gzip.to_vec()?).await?; Ok(()) } @@ -1006,9 +1006,9 @@ async fn download_version_v2(cookie:Cookie,asset_id:AssetID,version:Option<u64>, println!("version:{}",info.version); let location=info.info.locations.first().ok_or(anyhow::Error::msg("No locations"))?; - let data=context.get_asset_v2_download(location).await?; + let maybe_gzip=context.get_asset_v2_download(location).await?; - tokio::fs::write(dest,data).await?; + tokio::fs::write(dest,maybe_gzip.to_vec()?).await?; Ok(()) } @@ -1024,7 +1024,7 @@ async fn download_list(cookie:Cookie,asset_id_file_map:AssetIDFileMap)->AResult< .buffer_unordered(CONCURRENT_REQUESTS) .for_each(|b:AResult<_>|async{ match b{ - Ok((dest,data))=>if let Err(e)=tokio::fs::write(dest,data).await{ + Ok((dest,maybe_gzip))=>if let Err(e)=(async||{tokio::fs::write(dest,maybe_gzip.to_vec()?).await})().await{ eprintln!("fs error: {}",e); }, Err(e)=>eprintln!("dl error: {}",e), @@ -1228,9 +1228,9 @@ async fn download_history(mut config:DownloadHistoryConfig)->AResult<()>{ let mut path=output_folder.clone(); path.push(format!("{}_v{}.rbxl",config.asset_id,version_number)); join_set.spawn(async move{ - let file=context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id:config.asset_id,version:Some(version_number)}).await?; + let maybe_gzip=context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id:config.asset_id,version:Some(version_number)}).await?; - tokio::fs::write(path,file).await?; + tokio::fs::write(path,maybe_gzip.to_vec()?).await?; Ok::<_,anyhow::Error>(()) }); @@ -1350,9 +1350,9 @@ struct DownloadDecompileConfig{ async fn download_decompile(config:DownloadDecompileConfig)->AResult<()>{ let context=CookieContext::new(config.cookie); - let file=context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id:config.asset_id,version:None}).await?; + let maybe_gzip=context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id:config.asset_id,version:None}).await?; - let dom=load_dom(std::io::Cursor::new(file))?; + let dom=maybe_gzip.read_with(load_dom,load_dom)?; let context=rox_compiler::DecompiledContext::from_dom(dom); context.write_files(rox_compiler::WriteConfig{ @@ -1532,8 +1532,8 @@ async fn download_and_decompile_history_into_git(config:DownloadAndDecompileHist .map(|asset_version|{ let context=context.clone(); tokio::task::spawn(async move{ - let file=context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id,version:Some(asset_version.assetVersionNumber)}).await?; - let dom=load_dom(std::io::Cursor::new(file))?; + let maybe_gzip=context.get_asset(rbx_asset::cookie::GetAssetRequest{asset_id,version:Some(asset_version.assetVersionNumber)}).await?; + let dom=maybe_gzip.read_with(load_dom,load_dom)?; Ok::<_,anyhow::Error>((asset_version,rox_compiler::DecompiledContext::from_dom(dom))) }) })) From 091a2a92f177d16dc21975416f17de736194c4f2 Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Thu, 10 Apr 2025 16:48:39 -0700 Subject: [PATCH 7/8] rbx_asset: v0.4.4 parse string ints + save intermediate allocation --- Cargo.lock | 2 +- rbx_asset/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c020c03..3648a8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1293,7 +1293,7 @@ dependencies = [ [[package]] name = "rbx_asset" -version = "0.4.3" +version = "0.4.4" dependencies = [ "bytes", "chrono", diff --git a/rbx_asset/Cargo.toml b/rbx_asset/Cargo.toml index 0120817..d41d068 100644 --- a/rbx_asset/Cargo.toml +++ b/rbx_asset/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rbx_asset" -version = "0.4.3" +version = "0.4.4" edition = "2021" publish = ["strafesnet"] repository = "https://git.itzana.me/StrafesNET/asset-tool" From 450b6a0829afbf89b05960c90c2d75673c1ba365 Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Thu, 10 Apr 2025 17:26:47 -0700 Subject: [PATCH 8/8] update deps --- Cargo.lock | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3648a8f..43c352e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,9 +212,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.17" +version = "1.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" dependencies = [ "jobserver", "libc", @@ -378,9 +378,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -874,9 +874,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -997,9 +997,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -1056,9 +1056,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c72f6929239626840b28f919ce8981a317fc5dc63ce25c30d2ab372f94886f" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1117,9 +1117,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags 2.9.0", "cfg-if", @@ -1149,9 +1149,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", @@ -1524,9 +1524,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "once_cell", "rustls-pki-types", @@ -1666,9 +1666,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" @@ -1795,9 +1795,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.1" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -2311,9 +2311,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "yoke"