1use super::compression::decode_block_with_keys;
9use crate::blte::encryption::TactKeyStore;
10use crate::error::{CascError, Result};
11use crate::util::io::{read_be_u24, read_be_u32};
12
13#[derive(Debug)]
15struct ChunkInfo {
16 compressed_size: u32,
17 #[allow(dead_code)]
18 decompressed_size: u32,
19 #[allow(dead_code)]
20 hash: [u8; 16],
21}
22
23pub fn decode_blte(data: &[u8]) -> Result<Vec<u8>> {
25 decode_blte_with_keys(data, None)
26}
27
28pub fn decode_blte_with_keys(data: &[u8], keystore: Option<&TactKeyStore>) -> Result<Vec<u8>> {
34 if data.len() < 8 {
35 return Err(CascError::InvalidFormat("BLTE data too short".into()));
36 }
37 if &data[0..4] != b"BLTE" {
38 return Err(CascError::InvalidMagic {
39 expected: "BLTE".into(),
40 found: String::from_utf8_lossy(&data[0..4]).into(),
41 });
42 }
43
44 let header_size = read_be_u32(&data[4..8]);
45
46 if header_size == 0 {
47 if data.len() <= 8 {
49 return Ok(Vec::new());
50 }
51 return decode_block_with_keys(&data[8..], keystore);
52 }
53
54 if data.len() < 12 {
56 return Err(CascError::InvalidFormat(
57 "BLTE chunk table too short".into(),
58 ));
59 }
60
61 let table_format = data[8];
62 if table_format != 0x0F {
63 return Err(CascError::InvalidFormat(format!(
64 "unsupported BLTE table format: 0x{:02X}",
65 table_format
66 )));
67 }
68
69 let num_blocks = read_be_u24(&data[9..12]) as usize;
70
71 let descriptors_start = 12;
73 let descriptors_end = descriptors_start + num_blocks * 24;
74 if data.len() < descriptors_end {
75 return Err(CascError::InvalidFormat(
76 "BLTE block descriptors truncated".into(),
77 ));
78 }
79
80 let mut chunks = Vec::with_capacity(num_blocks);
81 for i in 0..num_blocks {
82 let base = descriptors_start + i * 24;
83 let compressed_size = read_be_u32(&data[base..]);
84 let decompressed_size = read_be_u32(&data[base + 4..]);
85 let mut hash = [0u8; 16];
86 hash.copy_from_slice(&data[base + 8..base + 24]);
87 chunks.push(ChunkInfo {
88 compressed_size,
89 decompressed_size,
90 hash,
91 });
92 }
93
94 let mut data_pos = header_size as usize;
96 let mut output = Vec::new();
97 for chunk in &chunks {
98 let block_end = data_pos + chunk.compressed_size as usize;
99 if data.len() < block_end {
100 return Err(CascError::InvalidFormat("BLTE block data truncated".into()));
101 }
102 let block = &data[data_pos..block_end];
103 let decoded = decode_block_with_keys(block, keystore)?;
104 output.extend_from_slice(&decoded);
105 data_pos = block_end;
106 }
107
108 Ok(output)
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use flate2::Compression;
115 use flate2::write::ZlibEncoder;
116 use std::io::Write;
117
118 fn zlib_compress(data: &[u8]) -> Vec<u8> {
119 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
120 encoder.write_all(data).unwrap();
121 encoder.finish().unwrap()
122 }
123
124 fn make_block_descriptor(compressed_size: u32, decompressed_size: u32) -> Vec<u8> {
125 let mut desc = Vec::new();
126 desc.extend_from_slice(&compressed_size.to_be_bytes());
127 desc.extend_from_slice(&decompressed_size.to_be_bytes());
128 desc.extend_from_slice(&[0u8; 16]); desc
130 }
131
132 #[test]
133 fn blte_validates_magic() {
134 let data = b"XBLT\x00\x00\x00\x00";
135 let err = decode_blte(data).unwrap_err();
136 assert!(matches!(err, CascError::InvalidMagic { .. }));
137 }
138
139 #[test]
140 fn blte_too_short() {
141 assert!(decode_blte(&[0x42, 0x4C, 0x54]).is_err());
142 }
143
144 #[test]
145 fn blte_single_block_raw() {
146 let mut data = Vec::new();
147 data.extend_from_slice(b"BLTE");
148 data.extend_from_slice(&0u32.to_be_bytes());
149 data.push(b'N');
150 data.extend_from_slice(b"hello");
151 assert_eq!(decode_blte(&data).unwrap(), b"hello");
152 }
153
154 #[test]
155 fn blte_single_block_zlib() {
156 let original = b"hello world compressed!";
157 let compressed = zlib_compress(original);
158
159 let mut data = Vec::new();
160 data.extend_from_slice(b"BLTE");
161 data.extend_from_slice(&0u32.to_be_bytes());
162 data.push(b'Z');
163 data.extend_from_slice(&compressed);
164
165 assert_eq!(decode_blte(&data).unwrap(), original);
166 }
167
168 #[test]
169 fn blte_single_block_empty() {
170 let mut data = Vec::new();
171 data.extend_from_slice(b"BLTE");
172 data.extend_from_slice(&0u32.to_be_bytes());
173 assert_eq!(decode_blte(&data).unwrap(), Vec::<u8>::new());
174 }
175
176 #[test]
177 fn blte_multi_block_two_raw() {
178 let block1_data = b"Nhello"; let block2_data = b"N world"; let desc1 = make_block_descriptor(block1_data.len() as u32, 5);
182 let desc2 = make_block_descriptor(block2_data.len() as u32, 6);
183
184 let header_size: u32 = 8 + 1 + 3 + 2 * 24; let mut data = Vec::new();
188 data.extend_from_slice(b"BLTE");
189 data.extend_from_slice(&header_size.to_be_bytes());
190 data.push(0x0F); data.push(0x00);
192 data.push(0x00);
193 data.push(0x02); data.extend_from_slice(&desc1);
195 data.extend_from_slice(&desc2);
196 assert_eq!(data.len(), header_size as usize);
197 data.extend_from_slice(block1_data);
198 data.extend_from_slice(block2_data);
199
200 let result = decode_blte(&data).unwrap();
201 assert_eq!(result, b"hello world");
202 }
203
204 #[test]
205 fn blte_multi_block_mixed_nz() {
206 let raw_content = b"raw part";
207 let zlib_content = b"compressed part";
208 let compressed = zlib_compress(zlib_content);
209
210 let block1 = {
211 let mut b = vec![b'N'];
212 b.extend_from_slice(raw_content);
213 b
214 };
215 let block2 = {
216 let mut b = vec![b'Z'];
217 b.extend_from_slice(&compressed);
218 b
219 };
220
221 let desc1 = make_block_descriptor(block1.len() as u32, raw_content.len() as u32);
222 let desc2 = make_block_descriptor(block2.len() as u32, zlib_content.len() as u32);
223
224 let header_size: u32 = 8 + 1 + 3 + 2 * 24;
225
226 let mut data = Vec::new();
227 data.extend_from_slice(b"BLTE");
228 data.extend_from_slice(&header_size.to_be_bytes());
229 data.push(0x0F);
230 data.push(0x00);
231 data.push(0x00);
232 data.push(0x02);
233 data.extend_from_slice(&desc1);
234 data.extend_from_slice(&desc2);
235 data.extend_from_slice(&block1);
236 data.extend_from_slice(&block2);
237
238 let result = decode_blte(&data).unwrap();
239 let expected: Vec<u8> = [raw_content.as_ref(), zlib_content.as_ref()].concat();
240 assert_eq!(result, expected);
241 }
242
243 #[test]
244 fn blte_multi_block_truncated_data() {
245 let header_size: u32 = 8 + 1 + 3 + 24;
246 let mut data = Vec::new();
247 data.extend_from_slice(b"BLTE");
248 data.extend_from_slice(&header_size.to_be_bytes());
249 data.push(0x0F);
250 data.push(0x00);
251 data.push(0x00);
252 data.push(0x01);
253 data.extend_from_slice(&make_block_descriptor(100, 100)); data.extend_from_slice(&[b'N', 1, 2, 3, 4]);
256
257 assert!(decode_blte(&data).is_err());
258 }
259
260 #[test]
261 fn blte_unsupported_table_format() {
262 let mut data = Vec::new();
263 data.extend_from_slice(b"BLTE");
264 data.extend_from_slice(&100u32.to_be_bytes());
265 data.push(0x10); data.extend_from_slice(&[0; 100]);
267
268 assert!(decode_blte(&data).is_err());
269 }
270
271 #[test]
272 fn blte_with_keys_none_works_for_non_encrypted() {
273 let mut data = Vec::new();
274 data.extend_from_slice(b"BLTE");
275 data.extend_from_slice(&0u32.to_be_bytes());
276 data.push(b'N');
277 data.extend_from_slice(b"test");
278 assert_eq!(decode_blte_with_keys(&data, None).unwrap(), b"test");
279 }
280
281 #[test]
282 fn blte_encrypted_block_without_keystore_errors() {
283 let mut data = Vec::new();
285 data.extend_from_slice(b"BLTE");
286 data.extend_from_slice(&0u32.to_be_bytes()); data.push(b'E'); data.push(1u8); data.push(8u8); data.extend_from_slice(&0xDEADu64.to_le_bytes()); data.extend_from_slice(&4u32.to_le_bytes()); data.extend_from_slice(&[0; 4]); data.push(b'S'); data.extend_from_slice(b"fake_encrypted_data");
295
296 assert!(decode_blte(&data).is_err());
298
299 let ks = TactKeyStore::new();
301 let result = decode_blte_with_keys(&data, Some(&ks));
302 assert!(result.is_err());
303 }
304
305 #[test]
306 fn blte_encrypted_single_block_round_trip() {
307 let key_name: u64 = 0xFA505078126ACB3E;
308 let ks = TactKeyStore::with_known_keys();
309 let key = ks.get(key_name).unwrap();
310
311 let plaintext = b"Ndecrypted!";
313 let iv_bytes = [0x10, 0x20, 0x30, 0x40];
314
315 let mut encrypted_payload = plaintext.to_vec();
317 {
318 use salsa20::Salsa20;
319 use salsa20::cipher::{KeyIvInit, StreamCipher};
320 let mut full_key = [0u8; 32];
321 full_key[..16].copy_from_slice(key);
322 full_key[16..].copy_from_slice(key);
323 let mut nonce = [0u8; 8];
324 nonce[..4].copy_from_slice(&iv_bytes);
325 let mut cipher = Salsa20::new(&full_key.into(), &nonce.into());
326 cipher.apply_keystream(&mut encrypted_payload);
327 }
328
329 let mut e_block = Vec::new();
331 e_block.push(1u8); e_block.push(8u8); e_block.extend_from_slice(&key_name.to_le_bytes());
334 e_block.extend_from_slice(&4u32.to_le_bytes()); e_block.extend_from_slice(&iv_bytes);
336 e_block.push(b'S'); e_block.extend_from_slice(&encrypted_payload);
338
339 let mut data = Vec::new();
341 data.extend_from_slice(b"BLTE");
342 data.extend_from_slice(&0u32.to_be_bytes()); data.push(b'E');
344 data.extend_from_slice(&e_block);
345
346 let result = decode_blte_with_keys(&data, Some(&ks)).unwrap();
347 assert_eq!(result, b"decrypted!");
348 }
349}