steamkit/util/
v_zstd_util.rs1use crate::error::SteamError;
4
5const VZSTD_HEADER: u32 = 0x615A_5356;
7
8const HEADER_LENGTH: usize = 8;
10
11const FOOTER_LENGTH: usize = 15;
13
14#[derive(Debug)]
16pub struct VZstdUtil;
17
18impl VZstdUtil {
19 pub fn decompress(
22 buffer: &[u8],
23 destination: &mut [u8],
24 verify_checksum: bool,
25 ) -> Result<i32, SteamError> {
26 if buffer.len() < HEADER_LENGTH + FOOTER_LENGTH {
27 return Err(SteamError::Decompression(format!(
28 "VZstd data is too short (was {} bytes).",
29 buffer.len()
30 )));
31 }
32
33 if u32::from_ne_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]) != VZSTD_HEADER {
35 return Err(SteamError::Decompression(
36 "Expecting VZstdHeader at start of stream".to_string(),
37 ));
38 }
39
40 let crc32 = i32::from_ne_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]);
41 let footer = buffer.len() - FOOTER_LENGTH;
42 let crc32_footer =
43 i32::from_ne_bytes([buffer[footer], buffer[footer + 1], buffer[footer + 2], buffer[footer + 3]]);
44 let size_decompressed = i32::from_ne_bytes([
45 buffer[footer + 4],
46 buffer[footer + 5],
47 buffer[footer + 6],
48 buffer[footer + 7],
49 ]);
50
51 debug_assert_eq!(crc32, crc32_footer);
53
54 if buffer[buffer.len() - 3] != b'z'
55 || buffer[buffer.len() - 2] != b's'
56 || buffer[buffer.len() - 1] != b'v'
57 {
58 return Err(SteamError::Decompression(
59 "Expecting VZstdFooter at end of stream".to_string(),
60 ));
61 }
62
63 if destination.len() < size_decompressed as usize {
64 return Err(SteamError::InvalidArgument(
65 "The destination buffer is smaller than the decompressed data size.".to_string(),
66 ));
67 }
68
69 let input = &buffer[HEADER_LENGTH..footer];
70
71 let decompressed = zstd::stream::decode_all(input)
72 .map_err(|_| SteamError::Decompression("Failed to decompress Zstd.".to_string()))?;
73
74 if decompressed.len() != size_decompressed as usize {
75 return Err(SteamError::Decompression(format!(
76 "Failed to decompress Zstd (expected {size_decompressed} bytes, got {}).",
77 decompressed.len()
78 )));
79 }
80
81 destination[..decompressed.len()].copy_from_slice(&decompressed);
82
83 if verify_checksum
84 && crc32fast::hash(&destination[..decompressed.len()]) != crc32_footer as u32
85 {
86 return Err(SteamError::Decompression(
87 "CRC does not match decompressed data. VZstd data may be corrupted.".to_string(),
88 ));
89 }
90
91 Ok(size_decompressed)
92 }
93}