1use crate::{Error, Result, ioutils::ReadInt, utils::jenkins3_hashpath};
9use modular_bitfield::{bitfield, prelude::*};
10use std::{
11 collections::{BTreeMap, HashMap},
12 fmt::Debug,
13 io::{ErrorKind, Read, Seek},
14 ops::BitAnd,
15};
16
17const TACT_MAGIC: &[u8; 4] = b"TSFM";
18const MD5_LENGTH: usize = 16;
19pub type Md5 = [u8; MD5_LENGTH];
20
21#[derive(Debug)]
22pub struct WowRootHeader {
23 pub use_old_record_format: bool,
24 pub version: u32,
25 pub total_file_count: u32,
26 pub named_file_count: u32,
27 pub allow_non_named_files: bool,
28}
29
30impl WowRootHeader {
31 pub fn parse<R: Read + Seek>(f: &mut R) -> Result<Self> {
33 let mut magic = [0; TACT_MAGIC.len()];
34 f.read_exact(&mut magic)?;
35 if &magic != TACT_MAGIC {
36 f.seek_relative(-(TACT_MAGIC.len() as i64))?;
38 return Ok(Self {
39 use_old_record_format: true,
40 version: 0,
41 total_file_count: 0,
42 named_file_count: 0,
43 allow_non_named_files: true,
44 });
45 }
46
47 let mut header_size = f.read_u32le()?;
49 let mut version = 0;
50 let total_file_count;
51
52 if header_size == 0x18 {
53 version = f.read_u32le()?;
55 total_file_count = f.read_u32le()?;
56 } else {
57 total_file_count = header_size;
58 header_size = 0;
59 }
60 let named_file_count = f.read_u32le()?;
61
62 if header_size == 0x18 {
63 f.seek_relative(4)?;
65 }
66
67 Ok(Self {
68 use_old_record_format: false,
69 allow_non_named_files: total_file_count != named_file_count,
70 version,
71 total_file_count,
72 named_file_count,
73 })
74 }
75}
76
77pub struct CasBlock {
78 pub flags: LocaleContentFlags,
79 pub fid_md5: Option<Vec<(u32, Md5)>>,
80 pub name_hash_fid: Option<Vec<(u64, u32)>>,
81}
82
83impl std::fmt::Debug for CasBlock {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct("CasBlock")
86 .field("context", &self.flags)
87 .field("fid_md5.len", &self.fid_md5.as_ref().map(|v| v.len()))
88 .field(
89 "name_hash_fid.len",
90 &self.name_hash_fid.as_ref().map(|v| v.len()),
91 )
92 .finish()
93 }
94}
95
96impl CasBlock {
97 pub fn parse<R: Read + Seek>(
98 f: &mut R,
99 header: &WowRootHeader,
100 only_locale: LocaleFlags,
101 ) -> Result<Self> {
102 let num_records = f.read_u32le()? as usize;
103
104 let flags = if header.version == 2 {
105 let locale = LocaleFlags::from(f.read_u32le()?);
106 let v1 = f.read_u32le()?;
107 let v2 = f.read_u32le()?;
108 let v3 = f.read_u8()?;
109
110 LocaleContentFlags {
111 locale,
112 content: ContentFlags::from(v1 | v2 | (u32::from(v3) << 17)),
113 }
114 } else {
115 LocaleContentFlags {
116 content: ContentFlags::from(f.read_u32le()?),
117 locale: LocaleFlags::from(f.read_u32le()?),
118 }
119 };
120
121 if num_records == 0 {
122 return Ok(Self {
124 flags,
125 fid_md5: None,
126 name_hash_fid: None,
127 });
128 }
129
130 let has_name_hashes = header.use_old_record_format
131 || !(header.allow_non_named_files && flags.content.no_name_hash());
132 if !flags.locale.all() && !(flags.locale & only_locale).any() {
133 let record_length =
137 size_of::<u32>() + MD5_LENGTH + if has_name_hashes { size_of::<u64>() } else { 0 };
138 f.seek_relative((num_records * record_length) as i64)?;
139
140 return Ok(Self {
141 flags,
142 fid_md5: None,
143 name_hash_fid: None,
144 });
145 }
146
147 let mut file_ids: Vec<u32> = Vec::with_capacity(num_records);
149 let mut file_id = 0u32;
150 for i in 0..num_records {
151 let delta = f.read_i32le()?;
152
153 file_id = if i == 0 {
154 u32::try_from(delta).map_err(|_| Error::FileIdDeltaOverflow)?
155 } else {
156 (file_id)
157 .checked_add_signed(1 + delta)
158 .ok_or(Error::FileIdDeltaOverflow)?
159 };
160
161 file_ids.push(file_id);
162 }
163
164 let mut fid_md5: Vec<(u32, Md5)> = Vec::with_capacity(num_records);
166 let mut name_hash_fid: Option<Vec<(u64, u32)>> = None;
167
168 if header.use_old_record_format {
169 let mut o = Vec::with_capacity(num_records);
170
171 for file_id in file_ids {
172 let mut md5 = [0; MD5_LENGTH];
173 f.read_exact(&mut md5)?;
174 fid_md5.push((file_id, md5));
175 o.push((f.read_u64le()?, file_id));
176 }
177
178 name_hash_fid = Some(o);
179 } else {
180 for &file_id in file_ids.iter() {
181 let mut md5 = [0; MD5_LENGTH];
182 f.read_exact(&mut md5)?;
183 fid_md5.push((file_id, md5));
184 }
185
186 if has_name_hashes {
187 let mut o = Vec::with_capacity(num_records);
188
189 for &file_id in file_ids.iter() {
190 let hash = f.read_u64le()?;
191 o.push((hash, file_id));
192 }
193
194 name_hash_fid = Some(o);
195 }
196 }
197
198 Ok(Self {
199 flags,
200 fid_md5: Some(fid_md5),
201 name_hash_fid,
202 })
203 }
204}
205
206#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
207pub struct LocaleContentFlags {
208 pub locale: LocaleFlags,
209 pub content: ContentFlags,
210}
211
212#[bitfield(bytes = 4)]
214#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, PartialOrd, Ord)]
215#[repr(u32)]
216pub struct LocaleFlags {
217 #[skip]
218 __: B1,
219 pub en_us: bool, #[skip]
221 __: B1,
222 pub ko_kr: bool, pub fr_fr: bool, pub de_de: bool, pub zh_cn: bool, pub es_es: bool, pub zh_tw: bool, pub en_gb: bool, pub en_cn: bool, pub en_tw: bool, pub es_mx: bool, pub ru_ru: bool, pub pt_br: bool, pub it_it: bool, pub pt_pt: bool, #[skip]
241 __: B15,
242}
243
244impl LocaleFlags {
245 pub fn any_locale() -> Self {
247 LocaleFlags::from(0xffffffff)
248 }
249
250 pub fn all(&self) -> bool {
252 self == &Self::any_locale()
253 }
254
255 pub fn any(&self) -> bool {
257 u32::from(*self) != 0
258 }
259}
260
261impl BitAnd for LocaleFlags {
262 type Output = LocaleFlags;
263
264 fn bitand(self, rhs: Self) -> Self::Output {
265 Self::from(u32::from(self) & u32::from(rhs))
266 }
267}
268
269#[bitfield(bytes = 4)]
273#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, PartialOrd, Ord)]
274#[repr(u32)]
275pub struct ContentFlags {
276 pub high_res_texture: bool, #[skip]
279 __: B1,
280 pub install: bool, pub windows: bool, pub macos: bool, pub x86_32: bool, pub x86_64: bool, pub low_violence: bool, pub mystery_platform: bool, #[skip]
297 __: B2,
298 pub update_plugin: bool, #[skip]
302 __: B3,
303 pub aarch64: bool, #[skip]
307 __: B11,
308 pub encrypted: bool, pub no_name_hash: bool, pub uncommon_resolution: bool, pub bundle: bool, pub no_compression: bool, }
316
317pub struct WowRoot {
321 pub fid_md5: BTreeMap<u32, BTreeMap<LocaleContentFlags, Md5>>,
323
324 pub name_hash_fid: HashMap<u64, u32>,
326}
327
328impl WowRoot {
329 pub fn parse<R: Read + Seek>(f: &mut R, only_locale: LocaleFlags) -> Result<Self> {
331 let header = WowRootHeader::parse(f)?;
332 let mut o = Self {
333 fid_md5: BTreeMap::new(),
334 name_hash_fid: HashMap::new(),
335 };
336
337 loop {
339 match CasBlock::parse(f, &header, only_locale) {
340 Ok(block) => {
341 if let Some(fid_md5) = block.fid_md5 {
345 for (k, v) in fid_md5 {
346 if let Some(e) = o.fid_md5.get_mut(&k) {
347 assert!(e.insert(block.flags, v).is_none());
348 } else {
349 o.fid_md5.insert(k, BTreeMap::from([(block.flags, v)]));
350 }
351 }
352 }
353
354 if let Some(name_hash_fid) = block.name_hash_fid {
355 for (k, v) in name_hash_fid {
356 o.name_hash_fid.entry(k).or_insert(v);
357 }
358 }
359 }
360
361 Err(Error::IOError(e)) if e.kind() == ErrorKind::UnexpectedEof => {
362 break;
363 }
364
365 Err(e) => return Err(e),
366 }
367 }
368 Ok(o)
369 }
370
371 pub fn get_fid(&self, path: &str) -> Option<u32> {
375 let hash = jenkins3_hashpath(path);
376 self.name_hash_fid.get(&hash).copied()
377 }
378}
379
380impl Debug for WowRoot {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 f.debug_struct("WowRoot")
383 .field("fid_md5.len", &self.fid_md5.len())
384 .field("name_hash_fid.len", &self.name_hash_fid.len())
385 .finish()
386 }
387}