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