1use std::{
2 fmt::{Display, Formatter},
3 io::{Read, Seek},
4 str::FromStr,
5 sync::Arc,
6};
7
8use anyhow::{anyhow, ensure};
9use binrw::{BinRead, Endian};
10
11use crate::{d2_shared::PackageNamedTagEntry, TagHash};
12
13pub trait ReadSeek: Read + Seek {}
14impl<R: Read + Seek> ReadSeek for R {}
15
16#[derive(Clone, Debug, bincode::Decode, bincode::Encode)]
17pub struct UEntryHeader {
18 pub reference: u32,
19 pub file_type: u8,
20 pub file_subtype: u8,
21 pub starting_block: u32,
22 pub starting_block_offset: u32,
23 pub file_size: u32,
24}
25
26#[derive(Clone)]
27pub struct UHashTableEntry {
28 pub hash64: u64,
29 pub hash32: TagHash,
30 pub reference: TagHash,
31}
32
33#[derive(BinRead, Debug, Copy, Clone)]
34#[br(repr = u16)]
35pub enum PackageLanguage {
36 None = 0,
37 English = 1,
38 French = 2,
39 Italian = 3,
40 German = 4,
41 Spanish = 5,
42 Japanese = 6,
43 Portuguese = 7,
44 Russian = 8,
45 Polish = 9,
46 SimplifiedChinese = 10,
47 TraditionalChinese = 11,
48 SpanishLatAm = 12,
49 Korean = 13,
50}
51
52impl PackageLanguage {
53 pub fn english_or_none(&self) -> bool {
54 matches!(self, Self::None | Self::English)
55 }
56}
57
58pub trait Package: Send + Sync {
59 fn endianness(&self) -> binrw::Endian;
60
61 fn pkg_id(&self) -> u16;
62 fn patch_id(&self) -> u16;
63
64 fn hash64_table(&self) -> Vec<UHashTableEntry>;
67
68 fn named_tags(&self) -> Vec<PackageNamedTagEntry>;
69
70 fn entries(&self) -> &[UEntryHeader];
71
72 fn entry(&self, index: usize) -> Option<UEntryHeader>;
73
74 fn language(&self) -> PackageLanguage;
75
76 fn platform(&self) -> PackagePlatform;
77
78 fn get_block(&self, index: usize) -> anyhow::Result<Arc<Vec<u8>>>;
81
82 fn read_entry(&self, index: usize) -> anyhow::Result<Vec<u8>> {
84 let entry = self
85 .entry(index)
86 .ok_or(anyhow!("Entry index is out of range"))?;
87
88 let mut buffer = Vec::with_capacity(entry.file_size as usize);
89 let mut current_offset = 0usize;
90 let mut current_block = entry.starting_block;
91
92 while current_offset < entry.file_size as usize {
93 let remaining_bytes = entry.file_size as usize - current_offset;
94 let block_data = self.get_block(current_block as usize)?;
95
96 if current_block == entry.starting_block {
97 let block_start_offset = entry.starting_block_offset as usize;
98 let block_remaining = block_data.len() - block_start_offset;
99 let copy_size = if block_remaining < remaining_bytes {
100 block_remaining
101 } else {
102 remaining_bytes
103 };
104
105 buffer.extend_from_slice(
106 &block_data[block_start_offset..block_start_offset + copy_size],
107 );
108
109 current_offset += copy_size;
110 } else if remaining_bytes < block_data.len() {
111 buffer.extend_from_slice(&block_data[..remaining_bytes]);
113 current_offset += remaining_bytes;
114 } else {
115 buffer.extend_from_slice(&block_data[..]);
117 current_offset += block_data.len();
118 }
119
120 current_block += 1;
121 }
122
123 Ok(buffer)
124 }
125
126 fn read_tag(&self, tag: TagHash) -> anyhow::Result<Vec<u8>> {
129 ensure!(tag.pkg_id() == self.pkg_id());
130 self.read_entry(tag.entry_index() as _)
131 }
132
133 fn get_all_by_reference(&self, reference: u32) -> Vec<(usize, UEntryHeader)> {
147 self.entries()
148 .iter()
149 .enumerate()
150 .filter(|(_, e)| e.reference == reference)
151 .map(|(i, e)| (i, e.clone()))
152 .collect()
153 }
154
155 fn get_all_by_type(&self, etype: u8, esubtype: Option<u8>) -> Vec<(usize, UEntryHeader)> {
156 self.entries()
157 .iter()
158 .enumerate()
159 .filter(|(_, e)| {
160 e.file_type == etype && esubtype.map(|t| t == e.file_subtype).unwrap_or(true)
161 })
162 .map(|(i, e)| (i, e.clone()))
163 .collect()
164 }
165
166 fn redaction_level(&self) -> Redaction {
167 Redaction::None
168 }
169}
170
171pub fn classify_file_prebl(ftype: u8, fsubtype: u8) -> String {
173 match (ftype, fsubtype) {
174 (26, 5) => "bnk".to_string(),
176 (26, 6) => "wem".to_string(),
178 (26, 7) => "hkx".to_string(),
180 (27, _) => "usm".to_string(),
182 (32, 1) => "texture.header".to_string(),
183 (32, 2) => "texture_cube.header".to_string(),
184 (32, 4) => "vertex.header".to_string(),
185 (32, 6) => "index.header".to_string(),
186 (40, 4) => "vertex.data".to_string(),
187 (40, 6) => "index.data".to_string(),
188 (48, 1) => "texture.data".to_string(),
189 (48, 2) => "texture_cube.data".to_string(),
190 (41, shader_type) => {
192 let ty = match shader_type {
193 0 => "fragment".to_string(),
194 1 => "vertex".to_string(),
195 6 => "compute".to_string(),
196 u => format!("unk{u}"),
197 };
198
199 format!("cso.{ty}")
200 }
201 (8, _) => "8080".to_string(),
202 _ => "bin".to_string(),
203 }
204}
205
206#[derive(
207 serde::Serialize,
208 serde::Deserialize,
209 clap::ValueEnum,
210 PartialEq,
211 Eq,
212 Debug,
213 Clone,
214 Copy,
215 BinRead,
216)]
217#[br(repr = u16)]
218pub enum PackagePlatform {
219 Tool32,
220 Win32,
221 Win64,
222 X360,
223 PS3,
224 Tool64,
225 Win64v1,
226 PS4,
227 XboxOne,
228 Stadia,
229 PS5,
230 Scarlett,
231}
232
233impl PackagePlatform {
234 pub fn endianness(&self) -> Endian {
235 match self {
236 Self::PS3 | Self::X360 => Endian::Big,
237 Self::XboxOne | Self::PS4 | Self::Win64 => Endian::Little,
238 _ => Endian::Little,
239 }
240 }
241}
242
243impl FromStr for PackagePlatform {
244 type Err = anyhow::Error;
245
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
247 Ok(match s {
248 "ps3" => Self::PS3,
249 "ps4" => Self::PS4,
250 "360" => Self::X360,
251 "w64" => Self::Win64,
252 "xboxone" => Self::XboxOne,
253 s => return Err(anyhow!("Invalid platform '{s}'")),
254 })
255 }
256}
257
258impl Display for PackagePlatform {
259 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
260 match self {
261 PackagePlatform::Tool32 => f.write_str("tool32"),
262 PackagePlatform::Win32 => f.write_str("w32"),
263 PackagePlatform::Win64 => f.write_str("w64"),
264 PackagePlatform::X360 => f.write_str("360"),
265 PackagePlatform::PS3 => f.write_str("ps3"),
266 PackagePlatform::Tool64 => f.write_str("tool64"),
267 PackagePlatform::Win64v1 => f.write_str("w64"),
268 PackagePlatform::PS4 => f.write_str("ps4"),
269 PackagePlatform::XboxOne => f.write_str("xboxone"),
270 PackagePlatform::Stadia => f.write_str("stadia"),
271 PackagePlatform::PS5 => f.write_str("ps5"),
272 PackagePlatform::Scarlett => f.write_str("scarlett"),
273 }
274 }
275}
276
277#[derive(PartialEq, Eq, Debug, Clone, Copy, bincode::Decode, bincode::Encode)]
278pub enum Redaction {
279 Full,
280 Partial,
281 None,
282}