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