#![allow(clippy::ptr_arg)]
use crate::ByteBuffer;
use crate::ByteSpan;
use crate::equipment::EquipSlot;
use crate::savedata::dat::DatHeader;
use binrw::NullString;
use binrw::binrw;
use binrw::{BinRead, BinWrite};
use std::collections::HashMap;
use std::io::Cursor;
use std::io::Read;
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<EquipSlot, GearSlot> {
slots
.iter()
.cloned()
.enumerate()
.filter_map(|(i, x)| match x.id {
0 => None,
_ => Some((EquipSlot::from_repr(i as u16)?, x)),
})
.collect()
}
fn convert_to_slots(slots: &HashMap<EquipSlot, GearSlot>) -> Vec<GearSlot> {
let mut result = vec![GearSlot::default(); NUMBER_OF_GEARSLOTS];
for (idx, slot) in slots.iter() {
result[*idx 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)]
pub id: u32,
#[br(map = convert_id_opt)]
#[bw(map = convert_opt_id)]
pub glamour_id: Option<u32>,
unknown1: u32,
unknown2: u32,
unknown3: u32,
unknown4: u32,
unknown5: u32,
}
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct GearSet {
pub index: u8,
#[brw(pad_size_to = 47)]
#[br(map = convert_to_string)]
#[bw(map = convert_from_string)]
pub name: String,
unknown1: u64,
#[br(map = convert_from_slots)]
#[bw(map = convert_to_slots)]
pub slots: HashMap<EquipSlot, GearSlot>,
#[br(map = convert_id_opt)]
#[bw(map = convert_opt_id)]
pub facewear: Option<u32>,
}
#[binrw]
#[br(little)]
#[derive(Debug, Clone)]
pub struct GearSets {
unknown1: u8,
pub current_gearset: u8,
unknown3: u16,
#[br(map = convert_from_gearsets)]
#[bw(map = convert_to_gearsets)]
pub gearsets: Vec<Option<GearSet>>,
}
const GEARSET_KEY: u8 = 0x73;
impl GearSets {
pub fn from_existing(buffer: ByteSpan) -> Option<GearSets> {
let mut cursor = Cursor::new(buffer);
let header = DatHeader::read(&mut cursor).ok()?;
let mut buffer = vec![0; header.content_size as usize - 1];
cursor.read_exact(&mut buffer).ok()?;
let decoded = buffer.iter().map(|x| *x ^ GEARSET_KEY).collect::<Vec<_>>();
let mut cursor = Cursor::new(decoded);
GearSets::read(&mut cursor).ok()
}
pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
let mut buffer = ByteBuffer::new();
{
let mut cursor = Cursor::new(&mut buffer);
let header = DatHeader {
file_type: crate::savedata::dat::DatFileType::Gearset,
file_version: 109,
max_size: 45205,
content_size: 45205,
};
header.write_le(&mut cursor).ok()?
}
{
let mut cursor = Cursor::new(ByteBuffer::new());
self.write_le(&mut cursor).ok()?;
buffer.extend_from_slice(
&cursor
.into_inner()
.iter()
.map(|x| *x ^ GEARSET_KEY)
.collect::<Vec<_>>(),
);
}
Some(buffer)
}
}
#[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");
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(&EquipSlot::MainHand).unwrap();
assert_eq!(slot.id, 5269);
assert_eq!(slot.glamour_id, Some(2453));
let slot = gearset.slots.get(&EquipSlot::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());
}
}