mirror of
https://github.com/redstrate/Physis.git
synced 2025-04-20 11:47:46 +00:00
feat: add GEARSET.DAT parsing (only content, it needs libxivdat to parse the wrapper)
This commit is contained in:
parent
af0d34c011
commit
8de836dcc3
4 changed files with 306 additions and 1 deletions
1
resources/tests/gearsets/simple.dat
Normal file
1
resources/tests/gearsets/simple.dat
Normal file
File diff suppressed because one or more lines are too long
|
@ -4,7 +4,7 @@
|
||||||
use crate::race::{Gender, Race, Subrace, get_race_id};
|
use crate::race::{Gender, Race, Subrace, get_race_id};
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
/// The slot the item is for.
|
/// The slot the item is for.
|
||||||
pub enum Slot {
|
pub enum Slot {
|
||||||
/// The head slot. Shorthand is "met".
|
/// The head slot. Shorthand is "met".
|
||||||
|
|
301
src/gearsets.rs
Normal file
301
src/gearsets.rs
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
use crate::ByteBuffer;
|
||||||
|
use crate::equipment::Slot;
|
||||||
|
use binrw::NullString;
|
||||||
|
use binrw::binrw;
|
||||||
|
use binrw::{BinRead, BinWrite};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::BufWriter;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
// FIXME: unclear what this is
|
||||||
|
const UNKNOWN_FLAG: u32 = 1_000_000;
|
||||||
|
|
||||||
|
fn convert_from_gear_id(id: u32) -> u32 {
|
||||||
|
id & !UNKNOWN_FLAG
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_gear_id(id: &u32) -> u32 {
|
||||||
|
id | UNKNOWN_FLAG
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_string(s: NullString) -> String {
|
||||||
|
s.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_from_string(s: &String) -> NullString {
|
||||||
|
NullString::from(s.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
const NUMBER_OF_GEARSETS: usize = 100;
|
||||||
|
|
||||||
|
fn convert_from_gearsets(gearsets: [GearSet; NUMBER_OF_GEARSETS]) -> Vec<Option<GearSet>> {
|
||||||
|
gearsets
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|x| if !x.name.is_empty() { Some(x) } else { None })
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_gearsets(gearsets: &Vec<Option<GearSet>>) -> Vec<GearSet> {
|
||||||
|
let mut result = vec![GearSet::default(); NUMBER_OF_GEARSETS];
|
||||||
|
for (i, gearset) in gearsets.iter().enumerate() {
|
||||||
|
if i >= NUMBER_OF_GEARSETS {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if let Some(gearset) = gearset {
|
||||||
|
result[i] = gearset.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
const NUMBER_OF_GEARSLOTS: usize = 14;
|
||||||
|
|
||||||
|
fn convert_from_slots(slots: [GearSlot; NUMBER_OF_GEARSLOTS]) -> HashMap<GearSlotType, GearSlot> {
|
||||||
|
slots
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, x)| match x.id {
|
||||||
|
0 => None,
|
||||||
|
_ => Some((i.try_into().ok()?, x)),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_slots(slots: &HashMap<GearSlotType, GearSlot>) -> Vec<GearSlot> {
|
||||||
|
let mut result = vec![GearSlot::default(); NUMBER_OF_GEARSLOTS];
|
||||||
|
for (idx, slot) in slots.iter() {
|
||||||
|
result[idx.clone() as usize] = slot.clone();
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_id_opt(id: u32) -> Option<u32> {
|
||||||
|
if id == 0 { None } else { Some(id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_opt_id(id: &Option<u32>) -> u32 {
|
||||||
|
id.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct GearSlot {
|
||||||
|
#[br(map = convert_from_gear_id)]
|
||||||
|
#[bw(map = convert_to_gear_id)]
|
||||||
|
/// The ID of the item.
|
||||||
|
pub id: u32,
|
||||||
|
#[br(map = convert_id_opt)]
|
||||||
|
#[bw(map = convert_opt_id)]
|
||||||
|
/// The ID of the item used as glamour.
|
||||||
|
pub glamour_id: Option<u32>,
|
||||||
|
// FIXME: one of those is most likely dyes, no idea about the rest
|
||||||
|
unknown1: u32,
|
||||||
|
unknown2: u32,
|
||||||
|
unknown3: u32,
|
||||||
|
unknown4: u32,
|
||||||
|
unknown5: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||||
|
pub enum GearSlotType {
|
||||||
|
MainHand = 0,
|
||||||
|
SecondaryHand,
|
||||||
|
Head,
|
||||||
|
Body,
|
||||||
|
Hands,
|
||||||
|
Waist, // legacy
|
||||||
|
Legs,
|
||||||
|
Feet,
|
||||||
|
Bracelets,
|
||||||
|
Necklace,
|
||||||
|
Earrings,
|
||||||
|
Ring1,
|
||||||
|
Ring2,
|
||||||
|
Soul,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GearSlotType {
|
||||||
|
pub fn to_slot(&self) -> Option<Slot> {
|
||||||
|
match self {
|
||||||
|
GearSlotType::Head => Some(Slot::Head),
|
||||||
|
GearSlotType::Body => Some(Slot::Body),
|
||||||
|
GearSlotType::Hands => Some(Slot::Hands),
|
||||||
|
GearSlotType::Legs => Some(Slot::Legs),
|
||||||
|
GearSlotType::Feet => Some(Slot::Feet),
|
||||||
|
GearSlotType::Bracelets => Some(Slot::Wrists),
|
||||||
|
GearSlotType::Necklace => Some(Slot::Neck),
|
||||||
|
GearSlotType::Earrings => Some(Slot::Earring),
|
||||||
|
GearSlotType::Ring1 => Some(Slot::RingLeft),
|
||||||
|
GearSlotType::Ring2 => Some(Slot::RingRight),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Slot> for GearSlotType {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(v: Slot) -> Result<Self, Self::Error> {
|
||||||
|
match v {
|
||||||
|
Slot::Head => Ok(GearSlotType::Head),
|
||||||
|
Slot::Body => Ok(GearSlotType::Body),
|
||||||
|
Slot::Hands => Ok(GearSlotType::Hands),
|
||||||
|
Slot::Legs => Ok(GearSlotType::Legs),
|
||||||
|
Slot::Feet => Ok(GearSlotType::Feet),
|
||||||
|
Slot::Wrists => Ok(GearSlotType::Bracelets),
|
||||||
|
Slot::Neck => Ok(GearSlotType::Necklace),
|
||||||
|
Slot::Earring => Ok(GearSlotType::Earrings),
|
||||||
|
Slot::RingLeft => Ok(GearSlotType::Ring1),
|
||||||
|
Slot::RingRight => Ok(GearSlotType::Ring2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<usize> for GearSlotType {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(v: usize) -> Result<Self, Self::Error> {
|
||||||
|
match v {
|
||||||
|
x if x == GearSlotType::MainHand as usize => Ok(GearSlotType::MainHand),
|
||||||
|
x if x == GearSlotType::SecondaryHand as usize => Ok(GearSlotType::SecondaryHand),
|
||||||
|
x if x == GearSlotType::Head as usize => Ok(GearSlotType::Head),
|
||||||
|
x if x == GearSlotType::Body as usize => Ok(GearSlotType::Body),
|
||||||
|
x if x == GearSlotType::Hands as usize => Ok(GearSlotType::Hands),
|
||||||
|
x if x == GearSlotType::Waist as usize => Ok(GearSlotType::Waist),
|
||||||
|
x if x == GearSlotType::Legs as usize => Ok(GearSlotType::Legs),
|
||||||
|
x if x == GearSlotType::Feet as usize => Ok(GearSlotType::Feet),
|
||||||
|
x if x == GearSlotType::Bracelets as usize => Ok(GearSlotType::Bracelets),
|
||||||
|
x if x == GearSlotType::Necklace as usize => Ok(GearSlotType::Necklace),
|
||||||
|
x if x == GearSlotType::Earrings as usize => Ok(GearSlotType::Earrings),
|
||||||
|
x if x == GearSlotType::Ring1 as usize => Ok(GearSlotType::Ring1),
|
||||||
|
x if x == GearSlotType::Ring2 as usize => Ok(GearSlotType::Ring2),
|
||||||
|
x if x == GearSlotType::Soul as usize => Ok(GearSlotType::Soul),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct GearSet {
|
||||||
|
/// The index of the gear set in the list of gear sets.
|
||||||
|
pub index: u8,
|
||||||
|
#[brw(pad_size_to = 47)]
|
||||||
|
#[br(map = convert_to_string)]
|
||||||
|
#[bw(map = convert_from_string)]
|
||||||
|
/// The name of the gear set.
|
||||||
|
pub name: String,
|
||||||
|
// FIXME: no clue what this is
|
||||||
|
unknown1: u64,
|
||||||
|
#[br(map = convert_from_slots)]
|
||||||
|
#[bw(map = convert_to_slots)]
|
||||||
|
/// The slots of the gear set.
|
||||||
|
pub slots: HashMap<GearSlotType, GearSlot>,
|
||||||
|
#[br(map = convert_id_opt)]
|
||||||
|
#[bw(map = convert_opt_id)]
|
||||||
|
/// The ID of the facewear item.
|
||||||
|
pub facewear: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[br(little)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GearSets {
|
||||||
|
// FIXME: can't be a version because it's always 0
|
||||||
|
unknown1: u8,
|
||||||
|
/// The index of the current active geat set.
|
||||||
|
pub current_gearset: u8,
|
||||||
|
// FIXME: no clue what this is
|
||||||
|
unknown3: u16,
|
||||||
|
#[br(map = convert_from_gearsets)]
|
||||||
|
#[bw(map = convert_to_gearsets)]
|
||||||
|
/// The list of gear sets.
|
||||||
|
pub gearsets: Vec<Option<GearSet>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const GEARSET_KEY: u8 = 0x73;
|
||||||
|
|
||||||
|
impl GearSets {
|
||||||
|
/// Parses existing gearsets data.
|
||||||
|
pub fn from_existing(buffer: &[u8]) -> Option<GearSets> {
|
||||||
|
let decoded = buffer.iter().map(|x| *x ^ GEARSET_KEY).collect::<Vec<_>>();
|
||||||
|
let mut cursor = Cursor::new(decoded);
|
||||||
|
GearSets::read(&mut cursor).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write existing gearsets data to a buffer.
|
||||||
|
pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
|
||||||
|
let mut buffer = ByteBuffer::new();
|
||||||
|
|
||||||
|
{
|
||||||
|
let cursor = Cursor::new(&mut buffer);
|
||||||
|
let mut writer = BufWriter::new(cursor);
|
||||||
|
|
||||||
|
self.write_le(&mut writer).ok()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let encoded = buffer.iter().map(|x| *x ^ GEARSET_KEY).collect::<Vec<_>>();
|
||||||
|
Some(encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::fs::read;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid() {
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.push("resources/tests");
|
||||||
|
d.push("random");
|
||||||
|
|
||||||
|
// Feeding it invalid data should not panic
|
||||||
|
GearSets::from_existing(&read(d).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn common_setup(name: &str) -> GearSets {
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.push("resources/tests/gearsets");
|
||||||
|
d.push(name);
|
||||||
|
|
||||||
|
GearSets::from_existing(&read(&d).unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_simple() {
|
||||||
|
let gearsets = common_setup("simple.dat");
|
||||||
|
|
||||||
|
assert_eq!(gearsets.current_gearset, 0);
|
||||||
|
let gearset = gearsets.gearsets[0].as_ref().unwrap();
|
||||||
|
for i in 1..gearsets.gearsets.len() {
|
||||||
|
assert!(gearsets.gearsets[i].is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(gearset.index, 0);
|
||||||
|
assert_eq!(gearset.name, "White Mage");
|
||||||
|
assert!(gearset.facewear.is_none());
|
||||||
|
assert_eq!(gearset.slots.len(), 2);
|
||||||
|
let slot = gearset.slots.get(&GearSlotType::MainHand).unwrap();
|
||||||
|
assert_eq!(slot.id, 5269);
|
||||||
|
assert_eq!(slot.glamour_id, Some(2453));
|
||||||
|
let slot = gearset.slots.get(&GearSlotType::Body).unwrap();
|
||||||
|
assert_eq!(slot.id, 8395913);
|
||||||
|
assert_eq!(slot.glamour_id, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_simple() {
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.push("resources/tests/gearsets");
|
||||||
|
d.push("simple.dat");
|
||||||
|
|
||||||
|
let gearset_bytes = &read(d).unwrap();
|
||||||
|
let gearset = GearSets::from_existing(gearset_bytes).unwrap();
|
||||||
|
assert_eq!(*gearset_bytes, gearset.write_to_buffer().unwrap());
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,6 +91,9 @@ pub mod cmp;
|
||||||
/// Reading and writing character data files (DAT) which are used in the character creator to save presets.
|
/// Reading and writing character data files (DAT) which are used in the character creator to save presets.
|
||||||
pub mod chardat;
|
pub mod chardat;
|
||||||
|
|
||||||
|
/// Reading and writing the content of gear sets (GEARSET.DAT) which are used to store a character's gear sets.
|
||||||
|
pub mod gearsets;
|
||||||
|
|
||||||
/// Reading and writing the plaintext config files (CFG) used by the game to store most of it's configuration.
|
/// Reading and writing the plaintext config files (CFG) used by the game to store most of it's configuration.
|
||||||
pub mod cfg;
|
pub mod cfg;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue