Skip to main content

tiger_pkg/
version.rs

1use std::sync::Arc;
2
3use binrw::Endian;
4
5use crate::{
6    d1_internal_alpha::PackageD1InternalAlpha, d1_legacy::PackageD1Legacy,
7    d1_roi::PackageD1RiseOfIron, d2_beta::PackageD2Beta, d2_beyondlight::PackageD2BeyondLight,
8    Package, PackageD2PreBL,
9};
10
11pub trait Version: clap::ValueEnum {
12    fn open(&self, path: &str) -> anyhow::Result<Arc<dyn Package>>;
13    fn endian(&self) -> Endian;
14    fn name(&self) -> &'static str;
15    fn id(&self) -> String {
16        self.to_possible_value()
17            .expect("Package version is missing an id/commandline value")
18            .get_name()
19            .to_string()
20    }
21
22    fn aes_key_0(&self) -> [u8; 16] {
23        [
24            0xD6, 0x2A, 0xB2, 0xC1, 0x0C, 0xC0, 0x1B, 0xC5, 0x35, 0xDB, 0x7B, 0x86, 0x55, 0xC7,
25            0xDC, 0x3B,
26        ]
27    }
28
29    fn aes_key_1(&self) -> [u8; 16] {
30        [
31            0x3A, 0x4A, 0x5D, 0x36, 0x73, 0xA6, 0x60, 0x58, 0x7E, 0x63, 0xE6, 0x76, 0xE4, 0x08,
32            0x92, 0xB5,
33        ]
34    }
35
36    fn aes_nonce_base(&self) -> [u8; 12] {
37        [
38            0x84, 0xDF, 0x11, 0xC0, 0xAC, 0xAB, 0xFA, 0x20, 0x33, 0x11, 0x26, 0x99,
39        ]
40    }
41}
42
43#[derive(serde::Serialize, serde::Deserialize, PartialEq, PartialOrd, Debug, Clone, Copy)]
44pub enum GameVersion {
45    Destiny(DestinyVersion),
46    Marathon(MarathonVersion),
47}
48
49impl GameVersion {
50    pub fn engine_version(&self) -> EngineVersion {
51        match self {
52            Self::Destiny(v) => v.engine_version(),
53            Self::Marathon(v) => v.engine_version(),
54        }
55    }
56}
57
58impl Version for GameVersion {
59    fn open(&self, path: &str) -> anyhow::Result<Arc<dyn Package>> {
60        match self {
61            Self::Destiny(v) => v.open(path),
62            Self::Marathon(v) => v.open(path),
63        }
64    }
65
66    fn endian(&self) -> Endian {
67        match self {
68            Self::Destiny(v) => v.endian(),
69            Self::Marathon(v) => v.endian(),
70        }
71    }
72
73    fn name(&self) -> &'static str {
74        match self {
75            Self::Destiny(v) => v.name(),
76            Self::Marathon(v) => v.name(),
77        }
78    }
79
80    fn aes_key_0(&self) -> [u8; 16] {
81        match self {
82            Self::Destiny(v) => v.aes_key_0(),
83            Self::Marathon(v) => v.aes_key_0(),
84        }
85    }
86
87    fn aes_key_1(&self) -> [u8; 16] {
88        match self {
89            Self::Destiny(v) => v.aes_key_1(),
90            Self::Marathon(v) => v.aes_key_1(),
91        }
92    }
93
94    fn aes_nonce_base(&self) -> [u8; 12] {
95        match self {
96            Self::Destiny(v) => v.aes_nonce_base(),
97            Self::Marathon(v) => v.aes_nonce_base(),
98        }
99    }
100}
101
102impl clap::ValueEnum for GameVersion {
103    fn value_variants<'a>() -> &'a [Self] {
104        &[
105            Self::Destiny(DestinyVersion::DestinyInternalAlpha),
106            Self::Destiny(DestinyVersion::DestinyFirstLookAlpha),
107            Self::Destiny(DestinyVersion::DestinyTheTakenKing),
108            Self::Destiny(DestinyVersion::DestinyRiseOfIron),
109            Self::Destiny(DestinyVersion::Destiny2Beta),
110            Self::Destiny(DestinyVersion::Destiny2Forsaken),
111            Self::Destiny(DestinyVersion::Destiny2Shadowkeep),
112            Self::Destiny(DestinyVersion::Destiny2BeyondLight),
113            Self::Destiny(DestinyVersion::Destiny2WitchQueen),
114            Self::Destiny(DestinyVersion::Destiny2Lightfall),
115            Self::Destiny(DestinyVersion::Destiny2TheFinalShape),
116            Self::Destiny(DestinyVersion::Destiny2TheEdgeOfFate),
117            Self::Destiny(DestinyVersion::Destiny2Renegades),
118            Self::Marathon(MarathonVersion::MarathonAlpha),
119            Self::Marathon(MarathonVersion::Marathon),
120        ]
121    }
122
123    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
124        match self {
125            Self::Destiny(v) => v.to_possible_value(),
126            Self::Marathon(v) => v.to_possible_value(),
127        }
128    }
129}
130
131#[derive(
132    serde::Serialize, serde::Deserialize, clap::ValueEnum, PartialEq, PartialOrd, Debug, Clone, Copy,
133)]
134// TODO(cohae): Revise retail version numbers when release rolls around
135pub enum MarathonVersion {
136    /// Closed alpha from April 2025
137    #[value(name = "goliath_alpha")]
138    MarathonAlpha = 500,
139
140    /// Release
141    #[value(name = "goliath")]
142    Marathon = 1000,
143}
144
145impl MarathonVersion {
146    pub fn engine_version(&self) -> EngineVersion {
147        match self {
148            Self::MarathonAlpha | Self::Marathon => EngineVersion::TigerGoliath,
149        }
150    }
151}
152
153impl Version for MarathonVersion {
154    fn open(&self, path: &str) -> anyhow::Result<Arc<dyn Package>> {
155        // nblock: Version (53) is the same as D2BeyondLight. Using Final Shape version for now.
156        Ok(Arc::new(PackageD2BeyondLight::open(
157            path,
158            DestinyVersion::Destiny2TheFinalShape,
159        )?))
160    }
161
162    fn endian(&self) -> Endian {
163        Endian::Little
164    }
165
166    fn name(&self) -> &'static str {
167        match self {
168            MarathonVersion::MarathonAlpha => "Marathon Closed Alpha",
169            MarathonVersion::Marathon => "Marathon",
170        }
171    }
172}
173
174#[derive(
175    serde::Serialize, serde::Deserialize, clap::ValueEnum, PartialEq, PartialOrd, Debug, Clone, Copy,
176)]
177pub enum DestinyVersion {
178    /// X360 december 2013 internal alpha version of Destiny
179    #[value(name = "d1_devalpha")]
180    DestinyInternalAlpha = 1_0500,
181
182    /// PS4 First Look Alpha
183    #[value(name = "d1_fla")]
184    DestinyFirstLookAlpha = 1_0800,
185
186    /// PS3/X360 version of Destiny (The Taken King)
187    #[value(name = "d1_ttk")]
188    DestinyTheTakenKing = 1_2000,
189
190    /// The latest version of Destiny (Rise of Iron)
191    #[value(name = "d1_roi")]
192    DestinyRiseOfIron = 1_2400,
193
194    /// Destiny 2 (Beta)
195    #[value(name = "d2_beta")]
196    Destiny2Beta = 2_1000,
197
198    /// Destiny 2 (Forsaken)
199    #[value(name = "d2_fs")]
200    Destiny2Forsaken = 2_2000,
201
202    /// The last version of Destiny 2 before Beyond Light (Shadowkeep/Season of Arrivals)
203    #[value(name = "d2_sk")]
204    Destiny2Shadowkeep = 2_2600,
205
206    /// Destiny 2 (Beyond Light/Season of the Lost)
207    #[value(name = "d2_bl")]
208    Destiny2BeyondLight = 2_3000,
209
210    /// Destiny 2 (Witch Queen/Season of the Seraph)
211    #[value(name = "d2_wq")]
212    Destiny2WitchQueen = 2_4000,
213
214    /// Destiny 2 (Lightfall)
215    #[value(name = "d2_lf")]
216    Destiny2Lightfall = 2_7000,
217
218    /// Destiny 2 (The Final Shape)
219    #[value(name = "d2_tfs")]
220    Destiny2TheFinalShape = 2_8000,
221
222    /// Destiny 2 (The Edge of Fate)
223    #[value(name = "d2_eof")]
224    Destiny2TheEdgeOfFate = 2_9000,
225
226    /// Destiny 2 (Renegades)
227    #[value(name = "d2_ren")]
228    Destiny2Renegades = 2_9500,
229}
230
231impl DestinyVersion {
232    pub fn is_d1(&self) -> bool {
233        *self <= DestinyVersion::DestinyRiseOfIron
234    }
235
236    pub fn is_d2(&self) -> bool {
237        *self >= DestinyVersion::Destiny2Beta
238    }
239
240    pub fn is_prebl(&self) -> bool {
241        DestinyVersion::Destiny2Beta <= *self && *self <= DestinyVersion::Destiny2Shadowkeep
242    }
243
244    pub fn engine_version(&self) -> EngineVersion {
245        match self {
246            DestinyVersion::DestinyInternalAlpha => EngineVersion::TigerD1Indev,
247            DestinyVersion::DestinyFirstLookAlpha => EngineVersion::TigerD1Alpha,
248            DestinyVersion::DestinyTheTakenKing => EngineVersion::TigerD1v1,
249            DestinyVersion::DestinyRiseOfIron => EngineVersion::TigerD1v2,
250            DestinyVersion::Destiny2Beta
251            | DestinyVersion::Destiny2Forsaken
252            | DestinyVersion::Destiny2Shadowkeep => EngineVersion::TigerD2v1,
253            DestinyVersion::Destiny2BeyondLight
254            | DestinyVersion::Destiny2WitchQueen
255            | DestinyVersion::Destiny2Lightfall
256            | DestinyVersion::Destiny2TheFinalShape
257            | DestinyVersion::Destiny2TheEdgeOfFate
258            | DestinyVersion::Destiny2Renegades => EngineVersion::TigerD2v2,
259        }
260    }
261}
262
263impl Version for DestinyVersion {
264    fn open(&self, path: &str) -> anyhow::Result<Arc<dyn Package>> {
265        Ok(match self {
266            DestinyVersion::DestinyInternalAlpha => Arc::new(PackageD1InternalAlpha::open(path)?),
267            DestinyVersion::DestinyFirstLookAlpha => Arc::new(PackageD1RiseOfIron::open(path)?),
268            DestinyVersion::DestinyTheTakenKing => Arc::new(PackageD1Legacy::open(path)?),
269            DestinyVersion::DestinyRiseOfIron => Arc::new(PackageD1RiseOfIron::open(path)?),
270            DestinyVersion::Destiny2Beta => Arc::new(PackageD2Beta::open(path)?),
271
272            DestinyVersion::Destiny2Forsaken | DestinyVersion::Destiny2Shadowkeep => {
273                Arc::new(PackageD2PreBL::open(path)?)
274            }
275
276            DestinyVersion::Destiny2BeyondLight
277            | DestinyVersion::Destiny2WitchQueen
278            | DestinyVersion::Destiny2Lightfall
279            | DestinyVersion::Destiny2TheFinalShape
280            | DestinyVersion::Destiny2TheEdgeOfFate
281            | DestinyVersion::Destiny2Renegades => {
282                Arc::new(PackageD2BeyondLight::open(path, *self)?)
283            }
284        })
285    }
286
287    fn endian(&self) -> Endian {
288        match self {
289            DestinyVersion::DestinyInternalAlpha | DestinyVersion::DestinyTheTakenKing => {
290                Endian::Big
291            }
292            _ => Endian::Little,
293        }
294    }
295
296    fn name(&self) -> &'static str {
297        match self {
298            DestinyVersion::DestinyInternalAlpha => "Destiny X360 Internal Alpha",
299            DestinyVersion::DestinyFirstLookAlpha => "Destiny First Look Alpha",
300            DestinyVersion::DestinyTheTakenKing => "Destiny: The Taken King",
301            DestinyVersion::DestinyRiseOfIron => "Destiny: Rise of Iron",
302            DestinyVersion::Destiny2Beta => "Destiny 2: Beta",
303            DestinyVersion::Destiny2Forsaken => "Destiny 2: Forsaken",
304            DestinyVersion::Destiny2Shadowkeep => "Destiny 2: Shadowkeep",
305            DestinyVersion::Destiny2BeyondLight => "Destiny 2: Beyond Light",
306            DestinyVersion::Destiny2WitchQueen => "Destiny 2: Witch Queen",
307            DestinyVersion::Destiny2Lightfall => "Destiny 2: Lightfall",
308            DestinyVersion::Destiny2TheFinalShape => "Destiny 2: The Final Shape",
309            DestinyVersion::Destiny2TheEdgeOfFate => "Destiny 2: The Edge of Fate",
310            DestinyVersion::Destiny2Renegades => "Destiny 2: Renegades",
311        }
312    }
313}
314
315/// Represents major engine versions
316#[derive(serde::Serialize, serde::Deserialize, PartialEq, PartialOrd, Debug, Clone, Copy)]
317pub enum EngineVersion {
318    TigerD1Indev = 1,
319    TigerD1Alpha = 5,
320    /// Destiny 1 Vanilla -> The Taken King
321    TigerD1v1 = 100,
322    /// Destiny 1: Rise of Iron
323    TigerD1v2 = 101,
324
325    /// Destiny 2 Vanilla -> Shadowkeep
326    TigerD2v1 = 200,
327    /// Destiny 2 Beyond Light -> Present
328    TigerD2v2 = 201,
329
330    /// Marathon
331    TigerGoliath = 300,
332}