1use byteorder::{BigEndian, ReadBytesExt};
9use std::collections::HashMap;
10use std::io::{Cursor, Read};
11use tracing::{debug, trace, warn};
12
13use crate::{Error, Result};
14
15const ENCODING_MAGIC: [u8; 2] = [0x45, 0x4E]; #[derive(Debug, Clone)]
20pub struct EncodingHeader {
21 pub magic: [u8; 2],
23 pub version: u8,
25 pub ckey_hash_size: u8,
27 pub ekey_hash_size: u8,
29 pub ckey_page_size_kb: u16,
31 pub ekey_page_size_kb: u16,
33 pub ckey_page_count: u32,
35 pub ekey_page_count: u32,
37 pub unk: u8,
39 pub espec_block_size: u32,
41}
42
43#[derive(Debug, Clone)]
45pub struct PageInfo {
46 pub first_hash: Vec<u8>,
48 pub checksum: [u8; 16],
50}
51
52#[derive(Debug, Clone)]
54pub struct EncodingEntry {
55 pub content_key: Vec<u8>,
57 pub encoding_keys: Vec<Vec<u8>>,
59 pub size: u64,
61}
62
63pub struct EncodingFile {
65 pub header: EncodingHeader,
67 ckey_entries: HashMap<Vec<u8>, EncodingEntry>,
69 ekey_to_ckey: HashMap<Vec<u8>, Vec<u8>>,
71}
72
73impl EncodingFile {
74 pub fn parse(data: &[u8]) -> Result<Self> {
76 let mut cursor = Cursor::new(data);
77
78 let header = Self::parse_header(&mut cursor)?;
80 debug!(
81 "Parsed encoding header: version={}, ckey_pages={}, ekey_pages={}, ckey_page_size_kb={}, ekey_page_size_kb={}, espec_table_size={}",
82 header.version,
83 header.ckey_page_count,
84 header.ekey_page_count,
85 header.ckey_page_size_kb,
86 header.ekey_page_size_kb,
87 header.espec_block_size
88 );
89
90 let mut espec_data = vec![0u8; header.espec_block_size as usize];
92 cursor.read_exact(&mut espec_data)?;
93 debug!("Read ESpec string table: {} bytes", espec_data.len());
94
95 let ckey_page_table = Self::parse_page_table(
97 &mut cursor,
98 header.ckey_page_count as usize,
99 header.ckey_hash_size as usize,
100 )?;
101 trace!("Parsed {} CKey page table entries", ckey_page_table.len());
102
103 let _ekey_page_table = Self::parse_page_table(
105 &mut cursor,
106 header.ekey_page_count as usize,
107 header.ekey_hash_size as usize,
108 )?;
109 trace!("Parsed {} EKey page table entries", _ekey_page_table.len());
110
111 let mut ckey_entries = HashMap::new();
113 let page_size = header.ckey_page_size_kb as usize * 1024;
114
115 let remaining_data = {
117 let current_pos = cursor.position() as usize;
118 &data[current_pos..]
119 };
120 let mut data_offset = 0;
121
122 for (i, page_info) in ckey_page_table.iter().enumerate() {
123 if data_offset + page_size <= remaining_data.len() {
125 let page_slice = &remaining_data[data_offset..data_offset + page_size];
126 let checksum = ::md5::compute(page_slice);
127
128 if checksum.as_ref() != page_info.checksum {
129 debug!(
130 "CKey page {} checksum mismatch (expected: {:?}, got: {:?})",
131 i,
132 hex::encode(page_info.checksum),
133 hex::encode(checksum.as_ref())
134 );
135 }
136
137 Self::parse_ckey_page(
138 page_slice,
139 header.ckey_hash_size,
140 header.ekey_hash_size,
141 &mut ckey_entries,
142 )?;
143 }
144
145 data_offset += page_size;
146 }
147
148 cursor.set_position(cursor.position() + (header.ckey_page_count as u64 * page_size as u64));
150
151 debug!("Parsed {} CKey entries", ckey_entries.len());
152
153 let mut ekey_to_ckey = HashMap::new();
155 for entry in ckey_entries.values() {
156 for ekey in &entry.encoding_keys {
157 ekey_to_ckey.insert(ekey.clone(), entry.content_key.clone());
158 }
159 }
160
161 debug!(
162 "Built EKey→CKey reverse mapping with {} entries",
163 ekey_to_ckey.len()
164 );
165
166 Ok(Self {
167 header,
168 ckey_entries,
169 ekey_to_ckey,
170 })
171 }
172
173 fn parse_header<R: Read>(reader: &mut R) -> Result<EncodingHeader> {
175 let mut magic = [0u8; 2];
176 reader.read_exact(&mut magic)?;
177
178 if magic != ENCODING_MAGIC {
179 return Err(Error::BadMagic);
180 }
181
182 let version = reader.read_u8()?;
183 if version != 1 {
184 warn!("Unexpected encoding version: {}", version);
185 }
186
187 let ckey_hash_size = reader.read_u8()?;
188 let ekey_hash_size = reader.read_u8()?;
189 let ckey_page_size_kb = reader.read_u16::<BigEndian>()?; let ekey_page_size_kb = reader.read_u16::<BigEndian>()?; let ckey_page_count = reader.read_u32::<BigEndian>()?; let ekey_page_count = reader.read_u32::<BigEndian>()?; let unk = reader.read_u8()?;
194 let espec_block_size = reader.read_u32::<BigEndian>()?; Ok(EncodingHeader {
197 magic,
198 version,
199 ckey_hash_size,
200 ekey_hash_size,
201 ckey_page_size_kb,
202 ekey_page_size_kb,
203 ckey_page_count,
204 ekey_page_count,
205 unk,
206 espec_block_size,
207 })
208 }
209
210 fn parse_page_table<R: Read>(
212 reader: &mut R,
213 page_count: usize,
214 hash_size: usize,
215 ) -> Result<Vec<PageInfo>> {
216 let mut pages = Vec::with_capacity(page_count);
217
218 for _ in 0..page_count {
219 let mut first_hash = vec![0u8; hash_size];
220 reader.read_exact(&mut first_hash)?;
221
222 let mut checksum = [0u8; 16];
223 reader.read_exact(&mut checksum)?;
224
225 pages.push(PageInfo {
226 first_hash,
227 checksum,
228 });
229 }
230
231 Ok(pages)
232 }
233
234 fn parse_ckey_page(
236 data: &[u8],
237 ckey_size: u8,
238 ekey_size: u8,
239 entries: &mut HashMap<Vec<u8>, EncodingEntry>,
240 ) -> Result<()> {
241 let mut offset = 0;
242
243 while offset < data.len() {
244 if offset + 6 > data.len() || data[offset..].iter().all(|&b| b == 0) {
246 break;
247 }
248
249 let key_count = data[offset];
251 offset += 1;
252
253 if key_count == 0 {
254 break; }
256
257 if offset + 5 > data.len() {
259 break;
260 }
261 let size = crate::utils::read_uint40_be(&data[offset..offset + 5])?;
262 offset += 5;
263
264 if offset + ckey_size as usize > data.len() {
266 break;
267 }
268 let ckey = data[offset..offset + ckey_size as usize].to_vec();
269 offset += ckey_size as usize;
270
271 let mut ekeys = Vec::new();
273 for _ in 0..key_count {
274 if offset + ekey_size as usize > data.len() {
275 break;
276 }
277 let ekey = data[offset..offset + ekey_size as usize].to_vec();
278 offset += ekey_size as usize;
279 ekeys.push(ekey);
280 }
281
282 entries.insert(
283 ckey.clone(),
284 EncodingEntry {
285 content_key: ckey,
286 encoding_keys: ekeys,
287 size,
288 },
289 );
290 }
291
292 Ok(())
293 }
294
295 pub fn lookup_by_ckey(&self, ckey: &[u8]) -> Option<&EncodingEntry> {
297 self.ckey_entries.get(ckey)
298 }
299
300 pub fn lookup_by_ekey(&self, ekey: &[u8]) -> Option<&Vec<u8>> {
302 self.ekey_to_ckey.get(ekey)
303 }
304
305 pub fn get_ekey_for_ckey(&self, ckey: &[u8]) -> Option<&Vec<u8>> {
307 self.ckey_entries
308 .get(ckey)
309 .and_then(|entry| entry.encoding_keys.first())
310 }
311
312 pub fn get_file_size(&self, ckey: &[u8]) -> Option<u64> {
314 self.ckey_entries.get(ckey).map(|entry| entry.size)
315 }
316
317 pub fn ckey_count(&self) -> usize {
319 self.ckey_entries.len()
320 }
321
322 pub fn ekey_count(&self) -> usize {
324 self.ekey_to_ckey.len()
325 }
326
327 pub fn get_sample_ckeys(&self, limit: usize) -> Vec<String> {
329 self.ckey_entries
330 .keys()
331 .take(limit)
332 .map(hex::encode)
333 .collect()
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn test_encoding_header_size() {
343 let header_size = 2 + 1 + 1 + 1 + 2 + 2 + 4 + 4 + 1 + 4;
345 assert_eq!(header_size, 22);
346 }
347
348 #[test]
349 fn test_parse_empty_encoding() {
350 let mut data = Vec::new();
352
353 data.extend_from_slice(&ENCODING_MAGIC);
355 data.push(1);
357 data.push(16); data.push(16); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&0u32.to_be_bytes()); data.push(0);
368 data.extend_from_slice(&0u32.to_be_bytes());
370
371 let result = EncodingFile::parse(&data);
372 assert!(result.is_ok());
373
374 let encoding = result.unwrap();
375 assert_eq!(encoding.header.version, 1);
376 assert_eq!(encoding.header.ckey_hash_size, 16);
377 assert_eq!(encoding.header.ekey_hash_size, 16);
378 assert_eq!(encoding.ckey_count(), 0);
379 assert_eq!(encoding.ekey_count(), 0);
380 }
381
382 #[test]
383 fn test_invalid_magic() {
384 let mut data = vec![0xFF, 0xFF]; data.push(1); let result = EncodingFile::parse(&data);
388 assert!(matches!(result, Err(Error::BadMagic)));
389 }
390}