use std::io::Cursor;
use aes::cipher::{
block_padding::Pkcs7, generic_array::GenericArray, BlockDecrypt, BlockDecryptMut, KeyInit,
KeyIvInit,
};
use crate::error::SteamError;
use crate::types::depot_manifest::ChunkData;
use crate::util::v_zip_util::VZipUtil;
use crate::util::v_zstd_util::VZstdUtil;
use crate::util::zip_util::ZipUtil;
const AES_BLOCK_SIZE: usize = 16;
const DEPOT_KEY_SIZE: usize = 32;
pub struct DepotChunk;
impl DepotChunk {
pub fn process(
info: &ChunkData,
data: &[u8],
destination: &mut [u8],
depot_key: &[u8],
) -> Result<usize, SteamError> {
if destination.len() < info.uncompressed_length as usize {
return Err(SteamError::InvalidArgument(format!(
"The destination buffer must be longer than the chunk UncompressedLength ({}).",
info.uncompressed_length
)));
}
if depot_key.len() != DEPOT_KEY_SIZE {
return Err(SteamError::InvalidArgument(format!(
"Tried to decrypt depot chunk with non {DEPOT_KEY_SIZE} byte key!"
)));
}
if data.len() < AES_BLOCK_SIZE {
return Err(SteamError::Malformed(format!(
"Not enough data in the depot chunk (was {} bytes).",
data.len()
)));
}
let cipher = aes::Aes256::new(GenericArray::from_slice(depot_key));
let mut iv = [0u8; AES_BLOCK_SIZE];
{
let mut block = GenericArray::clone_from_slice(&data[..AES_BLOCK_SIZE]);
cipher.decrypt_block(&mut block);
iv.copy_from_slice(block.as_slice());
}
let ciphertext = &data[AES_BLOCK_SIZE..];
if ciphertext.is_empty() || ciphertext.len() % AES_BLOCK_SIZE != 0 {
return Err(SteamError::Malformed(format!(
"The depot chunk has an invalid length (was {} bytes).",
data.len()
)));
}
let mut buffer = ciphertext.to_vec();
let written = {
let decryptor = cbc::Decryptor::<aes::Aes256>::new_from_slices(depot_key, &iv)
.map_err(|_| SteamError::Crypto("Invalid depot key.".to_string()))?;
let plaintext = decryptor
.decrypt_padded_mut::<Pkcs7>(&mut buffer)
.map_err(|_| SteamError::Crypto("Failed to decrypt the depot chunk.".to_string()))?;
plaintext.len()
};
if written < 16 {
return Err(SteamError::Decompression(format!(
"Not enough data in the decrypted depot chunk (was {written} bytes)."
)));
}
let plaintext = &buffer[..written];
let written_decompressed: i32 = if plaintext.starts_with(b"VSZa") {
VZstdUtil::decompress(plaintext, destination, false)?
} else if plaintext.starts_with(b"VZa") {
VZipUtil::decompress(plaintext, destination, false)?
} else if plaintext.starts_with(b"PK\x03\x04") {
let mut stream = Cursor::new(plaintext.to_vec());
ZipUtil::decompress(&mut stream, destination, false)?
} else {
return Err(SteamError::Decompression(format!(
"Unexpected depot chunk compression (first four bytes are {}).",
hex::encode_upper(&plaintext[..4])
)));
};
if info.uncompressed_length as i32 != written_decompressed {
return Err(SteamError::Decompression(format!(
"Processed data checksum failed to decompressed to the expected chunk uncompressed length. (was {written_decompressed}, should be {})",
info.uncompressed_length
)));
}
let written_decompressed = usize::try_from(written_decompressed).map_err(|_| {
SteamError::Decompression(
"The decompressed depot chunk has a negative length.".to_string(),
)
})?;
let data_crc = DepotChunk::adler_hash(&destination[..written_decompressed]);
if data_crc != info.checksum {
return Err(SteamError::Decompression(
"Processed data checksum is incorrect! Downloaded depot chunk is corrupt or invalid/wrong depot key?".to_string(),
));
}
Ok(written_decompressed)
}
pub fn adler_hash(input: &[u8]) -> u32 {
const BASE: u32 = 65521;
let mut s1: u32 = 0;
let mut s2: u32 = 0;
for &byte in input {
s1 = (s1 + u32::from(byte)) % BASE;
s2 = (s2 + s1) % BASE;
}
(s2 << 16) | s1
}
}