use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::error::SteamError;
pub(crate) fn date_time_from_unix_time(unix_time: u64) -> SystemTime {
UNIX_EPOCH + Duration::from_secs(unix_time)
}
pub(crate) fn date_time_to_unix_time(time: SystemTime) -> u64 {
time.duration_since(UNIX_EPOCH)
.map(|delta| delta.as_secs())
.unwrap_or(0)
}
pub(crate) struct ByteReader<'a> {
data: &'a [u8],
position: usize,
}
impl<'a> ByteReader<'a> {
pub(crate) fn new(data: &'a [u8]) -> ByteReader<'a> {
ByteReader { data, position: 0 }
}
pub(crate) fn position(&self) -> u64 {
self.position as u64
}
pub(crate) fn read_u32(&mut self) -> Result<u32, SteamError> {
let bytes = self.read_exact(4)?;
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
pub(crate) fn read_u64(&mut self) -> Result<u64, SteamError> {
let bytes = self.read_exact(8)?;
Ok(u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]))
}
pub(crate) fn read_bytes(&mut self, count: usize) -> Vec<u8> {
let end = (self.position + count).min(self.data.len());
let bytes = self.data[self.position..end].to_vec();
self.position = end;
bytes
}
pub(crate) fn read_null_term_string(&mut self) -> Result<String, SteamError> {
let start = self.position;
let mut end = start;
while end < self.data.len() && self.data[end] != 0 {
end += 1;
}
let bytes = &self.data[start..end];
self.position = if end < self.data.len() { end + 1 } else { end };
Ok(String::from_utf8_lossy(bytes).into_owned())
}
fn read_exact(&mut self, count: usize) -> Result<Vec<u8>, SteamError> {
let end = self.position + count;
if end > self.data.len() {
return Err(unexpected_eof());
}
let bytes = self.data[self.position..end].to_vec();
self.position = end;
Ok(bytes)
}
}
fn unexpected_eof() -> SteamError {
SteamError::Malformed("unexpected end of stream".to_string())
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct Steam3ManifestChunk {
pub chunk_gid: Option<Vec<u8>>,
pub checksum: u32,
pub offset: u64,
pub decompressed_size: u32,
pub compressed_size: u32,
}
impl Steam3ManifestChunk {
fn deserialize(reader: &mut ByteReader<'_>) -> Result<Steam3ManifestChunk, SteamError> {
let chunk_gid = reader.read_bytes(20);
Ok(Steam3ManifestChunk {
chunk_gid: Some(chunk_gid),
checksum: reader.read_u32()?,
offset: reader.read_u64()?,
decompressed_size: reader.read_u32()?,
compressed_size: reader.read_u32()?,
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct Steam3ManifestFileMapping {
pub file_name: Option<String>,
pub total_size: u64,
pub flags: u32,
pub hash_content: Option<Vec<u8>>,
pub hash_file_name: Option<Vec<u8>>,
pub num_chunks: u32,
pub chunks: Vec<Steam3ManifestChunk>,
}
impl Steam3ManifestFileMapping {
fn deserialize(reader: &mut ByteReader<'_>) -> Result<Steam3ManifestFileMapping, SteamError> {
let file_name = reader.read_null_term_string()?;
let total_size = reader.read_u64()?;
let flags = reader.read_u32()?;
let hash_content = reader.read_bytes(20);
let hash_file_name = reader.read_bytes(20);
let num_chunks = reader.read_u32()?;
let mut chunks = Vec::with_capacity(num_chunks as usize);
for _ in 0..num_chunks {
chunks.push(Steam3ManifestChunk::deserialize(reader)?);
}
Ok(Steam3ManifestFileMapping {
file_name: Some(file_name),
total_size,
flags,
hash_content: Some(hash_content),
hash_file_name: Some(hash_file_name),
num_chunks,
chunks,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Steam3Manifest {
pub magic: u32,
pub version: u32,
pub depot_id: u32,
pub manifest_gid: u64,
pub creation_time: SystemTime,
pub are_file_names_encrypted: bool,
pub total_uncompressed_size: u64,
pub total_compressed_size: u64,
pub chunk_count: u32,
pub file_entry_count: u32,
pub file_mapping_size: u32,
pub encrypted_crc: u32,
pub decrypted_crc: u32,
pub flags: u32,
pub mapping: Vec<Steam3ManifestFileMapping>,
}
impl Default for Steam3Manifest {
fn default() -> Steam3Manifest {
Steam3Manifest {
magic: 0,
version: 0,
depot_id: 0,
manifest_gid: 0,
creation_time: UNIX_EPOCH,
are_file_names_encrypted: false,
total_uncompressed_size: 0,
total_compressed_size: 0,
chunk_count: 0,
file_entry_count: 0,
file_mapping_size: 0,
encrypted_crc: 0,
decrypted_crc: 0,
flags: 0,
mapping: Vec::new(),
}
}
}
impl Steam3Manifest {
pub const MAGIC: u32 = 0x1634_9781;
const CURRENT_VERSION: u32 = 4;
pub(crate) fn deserialize(&mut self, reader: &mut ByteReader<'_>) -> Result<(), SteamError> {
self.version = reader.read_u32()?;
if self.version != Self::CURRENT_VERSION {
return Err(SteamError::Malformed(format!(
"Only version {} is supported.",
Self::CURRENT_VERSION
)));
}
self.depot_id = reader.read_u32()?;
self.manifest_gid = reader.read_u64()?;
self.creation_time = date_time_from_unix_time(reader.read_u32()? as u64);
self.are_file_names_encrypted = reader.read_u32()? != 0;
self.total_uncompressed_size = reader.read_u64()?;
self.total_compressed_size = reader.read_u64()?;
self.chunk_count = reader.read_u32()?;
self.file_entry_count = reader.read_u32()?;
self.file_mapping_size = reader.read_u32()?;
self.encrypted_crc = reader.read_u32()?;
self.decrypted_crc = reader.read_u32()?;
self.flags = reader.read_u32()?;
let mut mapping = Vec::new();
let mut remaining = self.file_mapping_size;
while remaining > 0 {
let start = reader.position();
let file_mapping = Steam3ManifestFileMapping::deserialize(reader)?;
mapping.push(file_mapping);
remaining = remaining.wrapping_sub((reader.position() - start) as u32);
}
self.mapping = mapping;
Ok(())
}
}