physis/
gearsets.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use crate::ByteBuffer;
5use crate::dat::DatHeader;
6use crate::equipment::Slot;
7use binrw::NullString;
8use binrw::binrw;
9use binrw::{BinRead, BinWrite};
10use std::collections::HashMap;
11use std::io::BufWriter;
12use std::io::Cursor;
13use std::io::Read;
14
15// FIXME: unclear what this is
16const UNKNOWN_FLAG: u32 = 1_000_000;
17
18fn convert_from_gear_id(id: u32) -> u32 {
19    id & !UNKNOWN_FLAG
20}
21
22fn convert_to_gear_id(id: &u32) -> u32 {
23    id | UNKNOWN_FLAG
24}
25
26fn convert_to_string(s: NullString) -> String {
27    s.to_string()
28}
29
30fn convert_from_string(s: &String) -> NullString {
31    NullString::from(s.as_str())
32}
33
34const NUMBER_OF_GEARSETS: usize = 100;
35
36fn convert_from_gearsets(gearsets: [GearSet; NUMBER_OF_GEARSETS]) -> Vec<Option<GearSet>> {
37    gearsets
38        .iter()
39        .cloned()
40        .map(|x| if !x.name.is_empty() { Some(x) } else { None })
41        .collect()
42}
43
44fn convert_to_gearsets(gearsets: &Vec<Option<GearSet>>) -> Vec<GearSet> {
45    let mut result = vec![GearSet::default(); NUMBER_OF_GEARSETS];
46    for (i, gearset) in gearsets.iter().enumerate() {
47        if i >= NUMBER_OF_GEARSETS {
48            break;
49        }
50        if let Some(gearset) = gearset {
51            result[i] = gearset.clone();
52        }
53    }
54    result
55}
56
57const NUMBER_OF_GEARSLOTS: usize = 14;
58
59fn convert_from_slots(slots: [GearSlot; NUMBER_OF_GEARSLOTS]) -> HashMap<GearSlotType, GearSlot> {
60    slots
61        .iter()
62        .cloned()
63        .enumerate()
64        .filter_map(|(i, x)| match x.id {
65            0 => None,
66            _ => Some((i.try_into().ok()?, x)),
67        })
68        .collect()
69}
70
71fn convert_to_slots(slots: &HashMap<GearSlotType, GearSlot>) -> Vec<GearSlot> {
72    let mut result = vec![GearSlot::default(); NUMBER_OF_GEARSLOTS];
73    for (idx, slot) in slots.iter() {
74        result[idx.clone() as usize] = slot.clone();
75    }
76    result
77}
78
79fn convert_id_opt(id: u32) -> Option<u32> {
80    if id == 0 { None } else { Some(id) }
81}
82
83fn convert_opt_id(id: &Option<u32>) -> u32 {
84    id.unwrap_or(0)
85}
86
87#[binrw]
88#[derive(Debug, Clone, Default)]
89pub struct GearSlot {
90    #[br(map = convert_from_gear_id)]
91    #[bw(map = convert_to_gear_id)]
92    /// The ID of the item.
93    pub id: u32,
94    #[br(map = convert_id_opt)]
95    #[bw(map = convert_opt_id)]
96    /// The ID of the item used as glamour.
97    pub glamour_id: Option<u32>,
98    // FIXME: one of those is most likely dyes, no idea about the rest
99    unknown1: u32,
100    unknown2: u32,
101    unknown3: u32,
102    unknown4: u32,
103    unknown5: u32,
104}
105
106#[derive(Debug, Clone, Hash, Eq, PartialEq)]
107pub enum GearSlotType {
108    MainHand = 0,
109    SecondaryHand,
110    Head,
111    Body,
112    Hands,
113    Waist, // legacy
114    Legs,
115    Feet,
116    Bracelets,
117    Necklace,
118    Earrings,
119    Ring1,
120    Ring2,
121    Soul,
122}
123
124impl GearSlotType {
125    pub fn to_slot(&self) -> Option<Slot> {
126        match self {
127            GearSlotType::Head => Some(Slot::Head),
128            GearSlotType::Body => Some(Slot::Body),
129            GearSlotType::Hands => Some(Slot::Hands),
130            GearSlotType::Legs => Some(Slot::Legs),
131            GearSlotType::Feet => Some(Slot::Feet),
132            GearSlotType::Bracelets => Some(Slot::Wrists),
133            GearSlotType::Necklace => Some(Slot::Neck),
134            GearSlotType::Earrings => Some(Slot::Earring),
135            GearSlotType::Ring1 => Some(Slot::RingLeft),
136            GearSlotType::Ring2 => Some(Slot::RingRight),
137            _ => None,
138        }
139    }
140}
141
142impl TryFrom<Slot> for GearSlotType {
143    type Error = ();
144
145    fn try_from(v: Slot) -> Result<Self, Self::Error> {
146        match v {
147            Slot::Head => Ok(GearSlotType::Head),
148            Slot::Body => Ok(GearSlotType::Body),
149            Slot::Hands => Ok(GearSlotType::Hands),
150            Slot::Legs => Ok(GearSlotType::Legs),
151            Slot::Feet => Ok(GearSlotType::Feet),
152            Slot::Wrists => Ok(GearSlotType::Bracelets),
153            Slot::Neck => Ok(GearSlotType::Necklace),
154            Slot::Earring => Ok(GearSlotType::Earrings),
155            Slot::RingLeft => Ok(GearSlotType::Ring1),
156            Slot::RingRight => Ok(GearSlotType::Ring2),
157        }
158    }
159}
160
161impl TryFrom<usize> for GearSlotType {
162    type Error = ();
163
164    fn try_from(v: usize) -> Result<Self, Self::Error> {
165        match v {
166            x if x == GearSlotType::MainHand as usize => Ok(GearSlotType::MainHand),
167            x if x == GearSlotType::SecondaryHand as usize => Ok(GearSlotType::SecondaryHand),
168            x if x == GearSlotType::Head as usize => Ok(GearSlotType::Head),
169            x if x == GearSlotType::Body as usize => Ok(GearSlotType::Body),
170            x if x == GearSlotType::Hands as usize => Ok(GearSlotType::Hands),
171            x if x == GearSlotType::Waist as usize => Ok(GearSlotType::Waist),
172            x if x == GearSlotType::Legs as usize => Ok(GearSlotType::Legs),
173            x if x == GearSlotType::Feet as usize => Ok(GearSlotType::Feet),
174            x if x == GearSlotType::Bracelets as usize => Ok(GearSlotType::Bracelets),
175            x if x == GearSlotType::Necklace as usize => Ok(GearSlotType::Necklace),
176            x if x == GearSlotType::Earrings as usize => Ok(GearSlotType::Earrings),
177            x if x == GearSlotType::Ring1 as usize => Ok(GearSlotType::Ring1),
178            x if x == GearSlotType::Ring2 as usize => Ok(GearSlotType::Ring2),
179            x if x == GearSlotType::Soul as usize => Ok(GearSlotType::Soul),
180            _ => Err(()),
181        }
182    }
183}
184
185#[binrw]
186#[derive(Debug, Clone, Default)]
187pub struct GearSet {
188    /// The index of the gear set in the list of gear sets.
189    pub index: u8,
190    #[brw(pad_size_to = 47)]
191    #[br(map = convert_to_string)]
192    #[bw(map = convert_from_string)]
193    /// The name of the gear set.
194    pub name: String,
195    // FIXME: no clue what this is
196    unknown1: u64,
197    #[br(map = convert_from_slots)]
198    #[bw(map = convert_to_slots)]
199    /// The slots of the gear set.
200    pub slots: HashMap<GearSlotType, GearSlot>,
201    #[br(map = convert_id_opt)]
202    #[bw(map = convert_opt_id)]
203    /// The ID of the facewear item.
204    pub facewear: Option<u32>,
205}
206
207#[binrw]
208#[br(little)]
209#[derive(Debug, Clone)]
210pub struct GearSets {
211    // FIXME: can't be a version because it's always 0
212    unknown1: u8,
213    /// The index of the current active geat set.
214    pub current_gearset: u8,
215    // FIXME: no clue what this is
216    unknown3: u16,
217    #[br(map = convert_from_gearsets)]
218    #[bw(map = convert_to_gearsets)]
219    /// The list of gear sets.
220    pub gearsets: Vec<Option<GearSet>>,
221}
222
223const GEARSET_KEY: u8 = 0x73;
224
225impl GearSets {
226    /// Parses existing gearsets data.
227    pub fn from_existing(buffer: &[u8]) -> Option<GearSets> {
228        let mut cursor = Cursor::new(buffer);
229
230        let header = DatHeader::read(&mut cursor).ok()?;
231
232        let mut buffer = vec![0; header.content_size as usize - 1];
233        cursor.read_exact(&mut buffer).ok()?;
234
235        let decoded = buffer.iter().map(|x| *x ^ GEARSET_KEY).collect::<Vec<_>>();
236        let mut cursor = Cursor::new(decoded);
237
238        GearSets::read(&mut cursor).ok()
239    }
240
241    /// Write existing gearsets data to a buffer.
242    pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
243        let mut buffer = ByteBuffer::new();
244
245        // header
246        {
247            let mut cursor = Cursor::new(&mut buffer);
248
249            let header = DatHeader {
250                file_type: crate::dat::DatFileType::Gearset,
251                max_size: 45205,
252                content_size: 45205,
253            };
254            header.write_le(&mut cursor).ok()?
255        }
256
257        // buffer contents encoded
258        {
259            let mut cursor = Cursor::new(ByteBuffer::new());
260            self.write_le(&mut cursor).ok()?;
261
262            buffer.extend_from_slice(
263                &cursor
264                    .into_inner()
265                    .iter()
266                    .map(|x| *x ^ GEARSET_KEY)
267                    .collect::<Vec<_>>(),
268            );
269        }
270
271        Some(buffer)
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use std::fs::read;
278    use std::path::PathBuf;
279
280    use super::*;
281
282    #[test]
283    fn test_invalid() {
284        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
285        d.push("resources/tests");
286        d.push("random");
287
288        // Feeding it invalid data should not panic
289        GearSets::from_existing(&read(d).unwrap());
290    }
291
292    fn common_setup(name: &str) -> GearSets {
293        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
294        d.push("resources/tests/gearsets");
295        d.push(name);
296
297        GearSets::from_existing(&read(&d).unwrap()).unwrap()
298    }
299
300    #[test]
301    fn read_simple() {
302        let gearsets = common_setup("simple.dat");
303
304        assert_eq!(gearsets.current_gearset, 0);
305        let gearset = gearsets.gearsets[0].as_ref().unwrap();
306        for i in 1..gearsets.gearsets.len() {
307            assert!(gearsets.gearsets[i].is_none());
308        }
309
310        assert_eq!(gearset.index, 0);
311        assert_eq!(gearset.name, "White Mage");
312        assert!(gearset.facewear.is_none());
313        assert_eq!(gearset.slots.len(), 2);
314        let slot = gearset.slots.get(&GearSlotType::MainHand).unwrap();
315        assert_eq!(slot.id, 5269);
316        assert_eq!(slot.glamour_id, Some(2453));
317        let slot = gearset.slots.get(&GearSlotType::Body).unwrap();
318        assert_eq!(slot.id, 8395913);
319        assert_eq!(slot.glamour_id, None);
320    }
321
322    #[test]
323    fn write_simple() {
324        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
325        d.push("resources/tests/gearsets");
326        d.push("simple.dat");
327
328        let gearset_bytes = &read(d).unwrap();
329        let gearset = GearSets::from_existing(gearset_bytes).unwrap();
330        assert_eq!(*gearset_bytes, gearset.write_to_buffer().unwrap());
331    }
332}