steamkit/util/
v_zip_util.rs1use crate::error::SteamError;
4use crate::util::lzma::compress::lzma::lzma_encoder::Encoder;
5use crate::util::lzma::i_coder::{
6 CoderPropID, CoderProperty, ICoder, ISetCoderProperties, IWriteCoderProperties,
7};
8use crate::util::lzma_decoder::LzmaDecoder;
9
10const VZIP_HEADER: u16 = 0x5A56;
12
13const VZIP_FOOTER: u16 = 0x767A;
15
16const HEADER_LENGTH: usize = 7;
18
19const FOOTER_LENGTH: usize = 10;
21
22const VERSION: u8 = b'a';
24
25#[derive(Debug)]
28pub struct Frame<'a> {
29 pub properties: &'a [u8],
31 pub compressed: &'a [u8],
33 pub output_crc: u32,
35 pub decompressed_size: i32,
37}
38
39#[derive(Debug)]
41pub struct VZipUtil;
42
43impl VZipUtil {
44 pub fn read_frame(buffer: &[u8], version: u8) -> Result<Frame<'_>, SteamError> {
48 if buffer.len() < HEADER_LENGTH + LzmaDecoder::PROPERTIES_SIZE + FOOTER_LENGTH {
49 return Err(SteamError::Decompression(format!(
50 "VZip data is too short (was {} bytes).",
51 buffer.len()
52 )));
53 }
54
55 let header = u16::from_le_bytes([buffer[0], buffer[1]]);
56
57 if header != VZIP_HEADER {
58 return Err(SteamError::Decompression(
59 "Expecting VZipHeader at start of stream".to_string(),
60 ));
61 }
62
63 if buffer[2] != version {
64 return Err(SteamError::Decompression(format!(
65 "Expecting VZip version '{}'",
66 version as char
67 )));
68 }
69
70 let footer = &buffer[buffer.len() - FOOTER_LENGTH..];
71 let output_crc = u32::from_le_bytes([footer[0], footer[1], footer[2], footer[3]]);
72 let size_decompressed = i32::from_le_bytes([footer[4], footer[5], footer[6], footer[7]]);
73
74 let footer_magic = u16::from_le_bytes([footer[8], footer[9]]);
75
76 if footer_magic != VZIP_FOOTER {
77 return Err(SteamError::Decompression(
78 "Expecting VZipFooter at end of stream".to_string(),
79 ));
80 }
81
82 if size_decompressed < 0 {
83 return Err(SteamError::Decompression(format!(
84 "VZip decompressed size is negative (was {size_decompressed})."
85 )));
86 }
87
88 let properties_start = HEADER_LENGTH;
89 let compressed_start = HEADER_LENGTH + LzmaDecoder::PROPERTIES_SIZE;
90 let compressed_end = buffer.len() - FOOTER_LENGTH;
91
92 Ok(Frame {
93 properties: &buffer[properties_start..compressed_start],
94 compressed: &buffer[compressed_start..compressed_end],
95 output_crc,
96 decompressed_size: size_decompressed,
97 })
98 }
99
100 pub fn decompress(
103 buffer: &[u8],
104 destination: &mut [u8],
105 verify_checksum: bool,
106 ) -> Result<i32, SteamError> {
107 let frame = VZipUtil::read_frame(buffer, VERSION)?;
108
109 if destination.len() < frame.decompressed_size as usize {
110 return Err(SteamError::InvalidArgument(
111 "The destination buffer is smaller than the decompressed data size.".to_string(),
112 ));
113 }
114
115 let output = &mut destination[..frame.decompressed_size as usize];
116
117 LzmaDecoder::decode(frame.properties, frame.compressed, output, 0)?;
118
119 if verify_checksum && crc32fast::hash(output) != frame.output_crc {
120 return Err(SteamError::Decompression(
121 "CRC does not match decompressed data. VZip data may be corrupted.".to_string(),
122 ));
123 }
124
125 Ok(frame.decompressed_size)
126 }
127
128 pub fn compress(buffer: &[u8]) -> Result<Vec<u8>, SteamError> {
130 let crc = crc32fast::hash(buffer);
131 let mut out: Vec<u8> = Vec::with_capacity(buffer.len() + 64);
132
133 out.extend_from_slice(&VZIP_HEADER.to_le_bytes());
134 out.push(VERSION);
135 out.extend_from_slice(&crc.to_le_bytes());
136
137 let mut encoder = Encoder::new();
138
139 encoder
140 .set_coder_properties(
141 &[
142 CoderPropID::DictionarySize,
143 CoderPropID::PosStateBits,
144 CoderPropID::LitContextBits,
145 CoderPropID::LitPosBits,
146 CoderPropID::Algorithm,
147 CoderPropID::NumFastBytes,
148 CoderPropID::MatchFinder,
149 CoderPropID::EndMarker,
150 ],
151 &[
152 CoderProperty::Int32(1 << 23),
153 CoderProperty::Int32(2),
154 CoderProperty::Int32(3),
155 CoderProperty::Int32(0),
156 CoderProperty::Int32(2),
157 CoderProperty::Int32(128),
158 CoderProperty::String("bt4".to_string()),
159 CoderProperty::Boolean(false),
160 ],
161 )?;
162
163 encoder.write_coder_properties(&mut out);
164 encoder.code(buffer.to_vec(), &mut out, -1, -1, None)?;
165
166 out.extend_from_slice(&crc.to_le_bytes());
167 out.extend_from_slice(&(buffer.len() as u32).to_le_bytes());
168 out.extend_from_slice(&VZIP_FOOTER.to_le_bytes());
169
170 Ok(out)
171 }
172}