1use std::collections::HashMap;
2
3use aes_gcm::{AeadInPlace, Aes128Gcm, KeyInit};
4use lazy_static::lazy_static;
5use parking_lot::RwLock;
6use tracing::{error, info};
7
8use crate::{d2_shared::BlockFlags, DestinyVersion, GameVersion, Version};
9
10lazy_static! {
11 static ref CIPHERS_EXTRA: RwLock<HashMap<u64, (Aes128Gcm, [u8; 12])>> = {
12 if let Ok(keyfile) = std::fs::read_to_string("keys.txt") {
13 let k: HashMap<u64, (Aes128Gcm, [u8; 12])> = parse_keys(&keyfile)
14 .into_iter()
15 .map(|(group, key, iv)| (group, (Aes128Gcm::new(&key.into()), iv)))
16 .collect();
17
18 if !k.is_empty() {
19 info!("Loaded {} external keys", k.len());
20 }
21
22 RwLock::new(k)
23 } else {
24 RwLock::new(HashMap::new())
25 }
26 };
27}
28
29pub fn register_pkg_key(group: u64, key: [u8; 16], iv: [u8; 12]) {
30 CIPHERS_EXTRA
31 .write()
32 .insert(group, (Aes128Gcm::new(&key.into()), iv));
33}
34
35pub fn has_pkg_key(group: u64) -> bool {
36 CIPHERS_EXTRA.read().contains_key(&group)
37}
38
39pub struct PkgGcmState {
40 nonce: [u8; 12],
41 cipher_0: Aes128Gcm,
42 cipher_1: Aes128Gcm,
43 cipher_extra: Option<(Aes128Gcm, [u8; 12])>,
44 group: u64,
45}
46
47impl PkgGcmState {
48 pub fn new(pkg_id: u16, version: GameVersion, group: u64) -> PkgGcmState {
49 let mut g = PkgGcmState {
50 nonce: version.aes_nonce_base(),
51 cipher_0: Aes128Gcm::new(&version.aes_key_0().into()),
52 cipher_1: Aes128Gcm::new(&version.aes_key_1().into()),
53 cipher_extra: CIPHERS_EXTRA.read().get(&group).cloned(),
54 group,
55 };
56
57 g.shift_nonce(pkg_id, version);
58
59 g
60 }
61
62 fn shift_nonce(&mut self, pkg_id: u16, version: GameVersion) {
63 match version {
64 GameVersion::Destiny(ver) => {
65 self.nonce[0] ^= (pkg_id >> 8) as u8;
66 match ver {
67 DestinyVersion::Destiny2Beta | DestinyVersion::Destiny2Shadowkeep => {
68 self.nonce[1] = 0xf9
69 }
70 _ => self.nonce[1] = 0xea,
71 }
72 self.nonce[11] ^= pkg_id as u8;
73 }
74 _ => unimplemented!(),
75 }
76 }
77
78 pub fn decrypt_block_in_place(
79 &self,
80 flags: BlockFlags,
81 tag: &[u8],
82 data: &mut [u8],
83 ) -> anyhow::Result<()> {
84 if flags.contains(BlockFlags::REDACTED) {
85 if let Some((cipher, iv)) = &self.cipher_extra {
86 if cipher
87 .decrypt_in_place_detached(iv.as_slice().into(), &[], data, tag.into())
88 .is_ok()
89 {
90 return Ok(());
91 }
92 }
93
94 return Err(anyhow::anyhow!(format!(
95 "No (working) key found for PKG group {:016X}",
96 self.group
97 )));
98 }
99
100 let (cipher, nonce) = if flags.contains(BlockFlags::ALT_CIPHER) {
101 (&self.cipher_1, &self.nonce)
102 } else {
103 (&self.cipher_0, &self.nonce)
104 };
105
106 match cipher.decrypt_in_place_detached(nonce.into(), &[], data, tag.into()) {
107 Ok(_) => Ok(()),
108 Err(_) => Err(anyhow::anyhow!("Failed to decrypt PKG data block")),
109 }
110 }
111}
112
113pub fn parse_keys(data: &str) -> Vec<(u64, [u8; 16], [u8; 12])> {
115 data.lines()
116 .enumerate()
117 .filter_map(|(i, l)| {
118 let mut parts = l.split(':');
119 let Some(group) = parts.next() else {
120 error!("Failed to parse group on line {i}");
121 return None;
122 };
123 let Some(key) = parts.next() else {
124 error!("Failed to parse key on line {i}");
125 return None;
126 };
127 let Some(iv) = parts.next().map(|p| p.chars().take(24).collect::<String>()) else {
128 error!("Failed to parse iv on line {i}");
129 return None;
130 };
131
132 let group = match u64::from_str_radix(group, 16) {
133 Ok(k) => k,
134 Err(e) => {
135 error!("Failed to parse group on line {i}: {e}");
136 return None;
137 }
138 };
139
140 let key = match hex::decode(key) {
141 Ok(data) => {
142 if data.len() != 16 {
143 error!("Invalid key length on line {i}");
144 return None;
145 }
146 let mut k = [0u8; 16];
147 k.copy_from_slice(&data);
148 k
149 }
150 Err(e) => {
151 error!("Failed to parse key on line {i}: {e}");
152 return None;
153 }
154 };
155
156 let iv = match hex::decode(iv) {
157 Ok(data) => {
158 if data.len() != 12 {
159 error!("Invalid iv length on line {i}");
160 return None;
161 }
162 let mut v = [0u8; 12];
163 v.copy_from_slice(&data);
164 v
165 }
166 Err(e) => {
167 error!("Failed to parse iv on line {i}: {e}");
168 return None;
169 }
170 };
171
172 Some((group, key, iv))
173 })
174 .collect()
175}