use std::{fmt::Debug, io::Read};
use crate::{dev::*, records::all::*};
use bitflags::bitflags;
#[derive(Debug, NomLE)]
pub struct RecordHeader {
pub iden: FourCC,
pub size: u32, pub flags: RecordFlags2,
pub form_id: FormId,
pub version_control: VersionControl,
}
#[derive(Debug, NomLE)]
pub struct VersionControl {
pub timestamp: ESMTimestamp,
pub users: [u8; 2],
pub form: u16,
pub revision: u16,
}
impl From<[u8; 8]> for VersionControl {
fn from(value: [u8; 8]) -> Self {
Self {
timestamp: ESMTimestamp(u16::from_le_bytes([value[0], value[1]])),
users: [value[2], value[3]],
form: u16::from_le_bytes([value[4], value[5]]),
revision: u16::from_le_bytes([value[6], value[7]]),
}
}
}
#[derive(NomLE)]
pub struct ESMTimestamp(pub u16);
impl std::fmt::Debug for ESMTimestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let year = self.0 >> 9;
let month = self.0 >> 5 & 0b00000001111;
let day = self.0 & 0b0000000000011111;
write!(f, "{:04}/{:02}/{:02}", year + 2000, month, day)
}
}
pub const TES4_MASTER: u32 = 0x1;
pub const UNKNOWN_FLAG_2: u32 = 0x2;
pub const UNKNOWN_FLAG_4: u32 = 0x4;
pub const DELETED_GROUP: u32 = 0x10;
pub const DELETED_RECORD: u32 = 0x20;
pub const GLOB_CONSTANT: u32 = 0x40;
pub const REFR_HIDDEN: u32 = 0x40;
pub const TES4_LOCALIZED: u32 = 0x80;
pub const MUST_UPDATE_ANIMS: u32 = 0x100;
pub const REFR_INACCESSIBLE: u32 = 0x100;
pub const TES4_LIGHT_MASTER: u32 = 0x200;
pub const REFR_HIDDEN2: u32 = 0x200;
pub const ACHR_STARTS_DEAD: u32 = 0x200;
pub const REFR_MOTION_BLUR_CASTS_SHADOWS: u32 = 0x200;
pub const QUEST_ITEM: u32 = 0x400;
pub const PERSISTENT_REFERENCE: u32 = 0x400;
pub const LSCR_DISPLAYS_IN_MAIN_MENU: u32 = 0x400;
pub const INITIALLY_DISABLED: u32 = 0x800;
pub const IGNORED: u32 = 0x1000;
pub const UNKNOWN_FLAG_2000: u32 = 0x2000;
pub const VISIBLE_WHEN_DISTANT: u32 = 0x8000;
pub const ACTI_RANDOM_ANIMATION_START: u32 = 0x10000;
pub const ACTI_DANGEROUS: u32 = 0x20000;
pub const OFF_LIMITS: u32 = 0x20000;
pub const COMPRESSED: u32 = 0x40000;
pub const CANT_WAIT: u32 = 0x80000;
pub const ACTI_IGNORE_OBJECT_INTERACTION: u32 = 0x100000;
pub const IS_MARKER: u32 = 0x800000;
pub const ACTI_OBSTACLE: u32 = 0x2000000;
pub const REFR_NO_AI_ACQUIRE: u32 = 0x2000000;
pub const NAVMESH_GEN_FILTER: u32 = 0x4000000;
pub const NAVMESH_GEN_BOUNDING_BOX: u32 = 0x8000000;
pub const FURN_MUST_EXIT_TO_TALK: u32 = 0x10000000;
pub const REFR_REFLECTED_BY_AUTO_WATER: u32 = 0x10000000;
pub const FURN_CHILD_CAN_USE: u32 = 0x20000000;
pub const IDLM_CHILD_CAN_USE: u32 = 0x20000000;
pub const REFR_DONT_HAVOK_SETTLE: u32 = 0x20000000;
pub const NAVMESH_GEN_GROUND: u32 = 0x40000000;
pub const REFR_NORESPAWN: u32 = 0x40000000;
pub const REFR_MULTIBOUND: u32 = 0x80000000;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RecordFlags2: u32 {
const COMPRESSED = 0x00040000;
const TES4_MASTER = 0x1;
const UNKNOWN_FLAG_2 = 0x2;
const UNKNOWN_FLAG_4 = 0x4;
const DELETED_GROUP = 0x10;
const DELETED_RECORD = 0x20;
const GLOB_CONSTANT = 0x40;
const REFR_HIDDEN = 0x40;
const TES4_LOCALIZED = 0x80;
const MUST_UPDATE_ANIMS = 0x100;
const REFR_INACCESSIBLE = 0x100;
const TES4_LIGHT_MASTER = 0x200;
const REFR_HIDDEN2 = 0x200;
const ACHR_STARTS_DEAD = 0x200;
const REFR_MOTION_BLUR_CASTS_SHADOWS = 0x200;
const QUEST_ITEM = 0x400;
const PERSISTENT_REFERENCE = 0x400;
const LSCR_DISPLAYS_IN_MAIN_MENU = 0x400;
const INITIALLY_DISABLED = 0x800;
const IGNORED = 0x1000;
const UNKNOWN_FLAG_2000 = 0x2000;
const VISIBLE_WHEN_DISTANT = 0x8000;
const ACTI_RANDOM_ANIMATION_START = 0x10000;
const ACTI_DANGEROUS = 0x20000;
const OFF_LIMITS = 0x20000;
const CANT_WAIT = 0x80000;
const ACTI_IGNORE_OBJECT_INTERACTION = 0x100000;
const IS_MARKER = 0x800000;
const ACTI_OBSTACLE = 0x2000000;
const REFR_NO_AI_ACQUIRE = 0x2000000;
const NAVMESH_GEN_FILTER = 0x4000000;
const NAVMESH_GEN_BOUNDING_BOX = 0x8000000;
const FURN_MUST_EXIT_TO_TALK = 0x10000000;
const REFR_REFLECTED_BY_AUTO_WATER = 0x10000000;
const FURN_CHILD_CAN_USE = 0x20000000;
const IDLM_CHILD_CAN_USE = 0x20000000;
const REFR_DONT_HAVOK_SETTLE = 0x20000000;
const NAVMESH_GEN_GROUND = 0x40000000;
const REFR_NORESPAWN = 0x40000000;
const REFR_MULTIBOUND = 0x80000000;
}
}
impl<'esm> Parse<&'esm[u8]> for RecordFlags2 {
fn parse(i: &'esm[u8]) -> IResult<&'esm[u8], Self, nom::error::Error<&'esm[u8]>> {
let (i, raw_flags) = le_u32::<&'esm[u8], nom::error::Error<&'esm[u8]>>(i)?;
Ok((i, RecordFlags2::from_bits_retain(raw_flags)))
}
}
impl RecordFlags2 {
pub fn is_compressed(&self) -> bool {
self.contains(RecordFlags2::COMPRESSED)
}
}
pub struct RawRecord<'esm> {
pub header: RecordHeader,
pub data: RawRecordData<'esm>,
}
impl<'esm> Parse<&'esm [u8]> for RawRecord<'esm> {
fn parse(i: &'esm[u8]) -> IResult<&'esm[u8], Self> {
let (i, (header, data)) = alloc_record(i)?;
if header.flags.is_compressed() {
if let Ok(dec) = decompress_record(data) {
Ok((i, Self{ header, data: RawRecordData::Decompressed(dec) }))
} else {
panic!("Could not decompress record: {:?}", header);
}
} else {
Ok((i, Self{ header, data: RawRecordData::Pointer(data) }))
}
}
}
impl Debug for RawRecord<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"RawRecord {{ header: {:?}, data: [{} bytes]}}",
self.header,
self.data.len()
)
}
}
impl RawRecord<'_> {
pub fn get_raw_fields(&self) -> IResult<&[u8], Vec<RawField<'_>>, nom::error::Error<&[u8]>> {
match &self.data {
RawRecordData::Pointer(data) => {
many0(RawField::parse)(data)
}
RawRecordData::Decompressed(data) => {
many0(RawField::parse)(data)
}
}
}
}
#[derive(Debug)]
pub enum RawRecordData<'esm> {
Pointer(&'esm[u8]),
Decompressed(Vec<u8>)
}
impl RawRecordData<'_> {
pub fn len(&self) -> usize {
match self {
RawRecordData::Pointer(items) => items.len(),
RawRecordData::Decompressed(items) => items.len(),
}
}
}
pub fn alloc_record(i: &[u8]) -> IResult<&[u8], (RecordHeader, &[u8]), nom::error::Error<&[u8]>> {
let orig = i;
let (i, header) = RecordHeader::parse(i)?;
let (i, raw) = take(header.size)(i)?;
if &header.iden.0 == b"GRUP" {
let (_, gheader) = GroupHeader::parse(orig)?;
panic!("alloc_record(): function encountered a group: {:?}", gheader);
} else {
Ok((i, (header, raw)))
}
}
#[derive(Debug)]
pub struct Record<T> {
pub header: RecordHeader,
pub fields: Vec<T>
}
impl<T: for<'esm> Parse<&'esm[u8]>> Parse<&[u8]> for Record<T> {
fn parse(i: &[u8]) -> IResult<&[u8], Self, nom::error::Error<&[u8]>> {
let (i, (header, raw)) = alloc_record(i)?;
if header.flags.is_compressed() {
if let Ok(dec) = decompress_record(raw) {
if let Ok((_, fields)) = many0(T::parse)(&dec) {
Ok((i, Self { header, fields }))
} else {
Err(nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Complete)))
}
} else {
panic!("Could not decompress record: {:?}", header);
}
} else if let Ok((_, fields)) = many0(T::parse)(raw) {
Ok((i, Self { header, fields }))
} else {
Err(nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Complete)))
}
}
}
pub fn decompress_record(i: &[u8]) -> Result<Vec<u8>, std::io::Error> {
if let Ok((i, real_size)) = le_u32::<&[u8], nom::error::Error<&[u8]>>(i) {
let mut buf = Vec::with_capacity(real_size as usize);
let mut dec = flate2::bufread::ZlibDecoder::new(i);
dec.read_to_end(&mut buf)?;
Ok(buf)
} else {
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "decompress_record(): could not get real size"))
}
}
#[derive(Debug)]
pub struct RawCellRecord<'esm> {
pub cell: RawRecord<'esm>,
pub cell_children: Option<RawCellChildren<'esm>>
}
impl RawCellRecord<'_> {
pub fn has_children(&self) -> bool {
self.cell_children.is_some()
}
}
impl <'esm> Parse<&'esm[u8]> for RawCellRecord<'esm> {
fn parse(i: &'esm[u8]) -> IResult<&'esm[u8], Self, nom::error::Error<&'esm[u8]>> {
let (i, cell) = RawRecord::parse(i)?;
println!("{:?}", cell);
let (_, ghead) = GroupHeader::parse(i)?;
match ghead.label {
GroupLabel::CellChildren(_) => {
let (i, cell_children) = RawCellChildren::parse(i)?;
Ok((i, Self { cell, cell_children: Some(cell_children) }))
}
_ => {
Ok((i, Self { cell, cell_children: None }))
}
}
}
}
#[derive(Debug)]
pub struct RawWorldRecord<'esm> {
pub world: RawRecord<'esm>,
pub world_children: Option<RawWorldChildren<'esm>>
}
impl RawWorldRecord<'_> {
pub fn has_children(&self) -> bool {
self.world_children.is_some()
}
}
impl <'esm> Parse<&'esm[u8]> for RawWorldRecord<'esm> {
fn parse(i: &'esm[u8]) -> IResult<&'esm[u8], Self> {
let (i, world) = RawRecord::parse(i)?;
let (_, ghead) = GroupHeader::parse(i)?;
match ghead.label {
GroupLabel::WorldChildren(_) => {
let (i, world_children) = RawWorldChildren::parse(i)?;
Ok((i, Self { world, world_children: Some(world_children) }))
}
_ => {
Ok((i, Self { world, world_children: None }))
}
}
}
}
#[derive(Debug)]
pub struct RawQuestRecord<'esm> {
pub quest: RawRecord<'esm>,
pub quest_children: Option<RawCellVisibleDistantChildren<'esm>>
}
impl RawQuestRecord<'_> {
pub fn has_children(&self) -> bool {
self.quest_children.is_some()
}
}
impl <'esm> Parse<&'esm[u8]> for RawQuestRecord<'esm> {
fn parse(i: &'esm[u8]) -> IResult<&'esm[u8], Self> {
let (i, quest) = RawRecord::parse(i)?;
let (_, next_id) = FourCC::parse(i)?;
if &next_id.0 != b"GRUP" {
return Ok((i, Self { quest, quest_children: None }));
}
let (_, ghead) = GroupHeader::parse(i)?;
match ghead.label {
GroupLabel::CellVisibleDistantChildren(_) => {
let (i, quest_children) = RawCellVisibleDistantChildren::parse(i)?;
Ok((i, Self { quest, quest_children: Some(quest_children) }))
}
_ => {
Ok((i, Self { quest, quest_children: None }))
}
}
}
}