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 Version for GameVersion {
50    fn open(&self, path: &str) -> anyhow::Result<Arc<dyn Package>> {
51        match self {
52            Self::Destiny(v) => v.open(path),
53            Self::Marathon(v) => v.open(path),
54        }
55    }
56
57    fn endian(&self) -> Endian {
58        match self {
59            Self::Destiny(v) => v.endian(),
60            Self::Marathon(v) => v.endian(),
61        }
62    }
63
64    fn name(&self) -> &'static str {
65        match self {
66            Self::Destiny(v) => v.name(),
67            Self::Marathon(v) => v.name(),
68        }
69    }
70
71    fn aes_key_0(&self) -> [u8; 16] {
72        match self {
73            Self::Destiny(v) => v.aes_key_0(),
74            Self::Marathon(v) => v.aes_key_0(),
75        }
76    }
77
78    fn aes_key_1(&self) -> [u8; 16] {
79        match self {
80            Self::Destiny(v) => v.aes_key_1(),
81            Self::Marathon(v) => v.aes_key_1(),
82        }
83    }
84
85    fn aes_nonce_base(&self) -> [u8; 12] {
86        match self {
87            Self::Destiny(v) => v.aes_nonce_base(),
88            Self::Marathon(v) => v.aes_nonce_base(),
89        }
90    }
91}
92
93impl clap::ValueEnum for GameVersion {
94    fn value_variants<'a>() -> &'a [Self] {
95        &[
96            Self::Destiny(DestinyVersion::DestinyInternalAlpha),
97            Self::Destiny(DestinyVersion::DestinyFirstLookAlpha),
98            Self::Destiny(DestinyVersion::DestinyTheTakenKing),
99            Self::Destiny(DestinyVersion::DestinyRiseOfIron),
100            Self::Destiny(DestinyVersion::Destiny2Beta),
101            Self::Destiny(DestinyVersion::Destiny2Forsaken),
102            Self::Destiny(DestinyVersion::Destiny2Shadowkeep),
103            Self::Destiny(DestinyVersion::Destiny2BeyondLight),
104            Self::Destiny(DestinyVersion::Destiny2WitchQueen),
105            Self::Destiny(DestinyVersion::Destiny2Lightfall),
106            Self::Destiny(DestinyVersion::Destiny2TheFinalShape),
107            Self::Destiny(DestinyVersion::Destiny2TheEdgeOfFate),
108            Self::Marathon(MarathonVersion::MarathonAlpha),
109        ]
110    }
111
112    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
113        match self {
114            Self::Destiny(v) => v.to_possible_value(),
115            Self::Marathon(v) => v.to_possible_value(),
116        }
117    }
118}
119
120#[derive(
121    serde::Serialize, serde::Deserialize, clap::ValueEnum, PartialEq, PartialOrd, Debug, Clone, Copy,
122)]
123pub enum MarathonVersion {
124    /// Closed alpha from April 2025
125    #[value(name = "ma_alpha")]
126    MarathonAlpha = 500,
127}
128
129impl Version for MarathonVersion {
130    fn open(&self, path: &str) -> anyhow::Result<Arc<dyn Package>> {
131        // nblock: Version (53) is the same as D2BeyondLight. Using Final Shape version for now.
132        Ok(Arc::new(PackageD2BeyondLight::open(
133            path,
134            DestinyVersion::Destiny2TheFinalShape,
135        )?))
136    }
137
138    fn endian(&self) -> Endian {
139        Endian::Little
140    }
141
142    fn name(&self) -> &'static str {
143        match self {
144            MarathonVersion::MarathonAlpha => "Marathon Closed Alpha",
145        }
146    }
147}
148
149#[derive(
150    serde::Serialize, serde::Deserialize, clap::ValueEnum, PartialEq, PartialOrd, Debug, Clone, Copy,
151)]
152pub enum DestinyVersion {
153    /// X360 december 2013 internal alpha version of Destiny
154    #[value(name = "d1_devalpha")]
155    DestinyInternalAlpha = 1_0500,
156
157    /// PS4 First Look Alpha
158    #[value(name = "d1_fla")]
159    DestinyFirstLookAlpha = 1_0800,
160
161    /// PS3/X360 version of Destiny (The Taken King)
162    #[value(name = "d1_ttk")]
163    DestinyTheTakenKing = 1_2000,
164
165    /// The latest version of Destiny (Rise of Iron)
166    #[value(name = "d1_roi")]
167    DestinyRiseOfIron = 1_2400,
168
169    /// Destiny 2 (Beta)
170    #[value(name = "d2_beta")]
171    Destiny2Beta = 2_1000,
172
173    /// Destiny 2 (Forsaken)
174    #[value(name = "d2_fs")]
175    Destiny2Forsaken = 2_2000,
176
177    /// The last version of Destiny 2 before Beyond Light (Shadowkeep/Season of Arrivals)
178    #[value(name = "d2_sk")]
179    Destiny2Shadowkeep = 2_2600,
180
181    /// Destiny 2 (Beyond Light/Season of the Lost)
182    #[value(name = "d2_bl")]
183    Destiny2BeyondLight = 2_3000,
184
185    /// Destiny 2 (Witch Queen/Season of the Seraph)
186    #[value(name = "d2_wq")]
187    Destiny2WitchQueen = 2_4000,
188
189    /// Destiny 2 (Lightfall)
190    #[value(name = "d2_lf")]
191    Destiny2Lightfall = 2_7000,
192
193    /// Destiny 2 (The Final Shape)
194    #[value(name = "d2_tfs")]
195    Destiny2TheFinalShape = 2_8000,
196
197    /// Destiny 2 (The Edge of Fate)
198    #[value(name = "d2_eof")]
199    Destiny2TheEdgeOfFate = 2_9000,
200}
201
202impl DestinyVersion {
203    pub fn is_d1(&self) -> bool {
204        *self <= DestinyVersion::DestinyRiseOfIron
205    }
206
207    pub fn is_d2(&self) -> bool {
208        *self >= DestinyVersion::Destiny2Beta
209    }
210
211    pub fn is_prebl(&self) -> bool {
212        DestinyVersion::Destiny2Beta <= *self && *self <= DestinyVersion::Destiny2Shadowkeep
213    }
214}
215
216impl Version for DestinyVersion {
217    fn open(&self, path: &str) -> anyhow::Result<Arc<dyn Package>> {
218        Ok(match self {
219            DestinyVersion::DestinyInternalAlpha => Arc::new(PackageD1InternalAlpha::open(path)?),
220            DestinyVersion::DestinyFirstLookAlpha => Arc::new(PackageD1RiseOfIron::open(path)?),
221            DestinyVersion::DestinyTheTakenKing => Arc::new(PackageD1Legacy::open(path)?),
222            DestinyVersion::DestinyRiseOfIron => Arc::new(PackageD1RiseOfIron::open(path)?),
223            DestinyVersion::Destiny2Beta => Arc::new(PackageD2Beta::open(path)?),
224
225            DestinyVersion::Destiny2Forsaken | DestinyVersion::Destiny2Shadowkeep => {
226                Arc::new(PackageD2PreBL::open(path)?)
227            }
228
229            DestinyVersion::Destiny2BeyondLight
230            | DestinyVersion::Destiny2WitchQueen
231            | DestinyVersion::Destiny2Lightfall
232            | DestinyVersion::Destiny2TheFinalShape
233            | DestinyVersion::Destiny2TheEdgeOfFate => {
234                Arc::new(PackageD2BeyondLight::open(path, *self)?)
235            }
236        })
237    }
238
239    fn endian(&self) -> Endian {
240        match self {
241            DestinyVersion::DestinyInternalAlpha | DestinyVersion::DestinyTheTakenKing => {
242                Endian::Big
243            }
244            _ => Endian::Little,
245        }
246    }
247
248    fn name(&self) -> &'static str {
249        match self {
250            DestinyVersion::DestinyInternalAlpha => "Destiny X360 Internal Alpha",
251            DestinyVersion::DestinyFirstLookAlpha => "Destiny First Look Alpha",
252            DestinyVersion::DestinyTheTakenKing => "Destiny: The Taken King",
253            DestinyVersion::DestinyRiseOfIron => "Destiny: Rise of Iron",
254            DestinyVersion::Destiny2Beta => "Destiny 2: Beta",
255            DestinyVersion::Destiny2Forsaken => "Destiny 2: Forsaken",
256            DestinyVersion::Destiny2Shadowkeep => "Destiny 2: Shadowkeep",
257            DestinyVersion::Destiny2BeyondLight => "Destiny 2: Beyond Light",
258            DestinyVersion::Destiny2WitchQueen => "Destiny 2: Witch Queen",
259            DestinyVersion::Destiny2Lightfall => "Destiny 2: Lightfall",
260            DestinyVersion::Destiny2TheFinalShape => "Destiny 2: The Final Shape",
261            DestinyVersion::Destiny2TheEdgeOfFate => "Destiny 2: The Edge of Fate",
262        }
263    }
264}