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