use std::io;
use byteorder::{ReadBytesExt as _, LE};
use indexmap::IndexMap;
use ltk_hash::BinHash;
use crate::{
property::{values, Kind},
stream::layout::{Cursor, Numbering},
BinObject, Error, PropertyValueEnum,
};
const PROBE: usize = 256;
const CHUNK: usize = 64 * 1024;
pub(crate) fn read_object(cur: &mut Cursor<'_>, class_hash: BinHash) -> Result<BinObject, Error> {
cur.sized_region(|cur| read_object_body(cur, class_hash))
}
fn read_object_body(cur: &mut Cursor<'_>, class_hash: BinHash) -> Result<BinObject, Error> {
let path_hash = cur.bin_hash()?;
let count = cur.u16()?;
Ok(BinObject {
path_hash,
class_hash,
properties: read_properties(cur, count)?,
})
}
pub(crate) fn read_properties(
cur: &mut Cursor<'_>,
count: u16,
) -> Result<IndexMap<BinHash, PropertyValueEnum>, Error> {
let mut properties = IndexMap::with_capacity(count as usize);
for _ in 0..count {
let name_hash = cur.bin_hash()?;
let kind = cur.kind()?;
properties.insert(name_hash, read_value(cur, kind)?);
}
Ok(properties)
}
pub(crate) fn read_value(cur: &mut Cursor<'_>, kind: Kind) -> Result<PropertyValueEnum, Error> {
use Kind as K;
Ok(match kind {
K::None => values::None::default().into(),
K::Bool => values::Bool::new(cur.bool()?).into(),
K::BitBool => values::BitBool::new(cur.bool()?).into(),
K::I8 => values::I8::new(cur.i8()?).into(),
K::U8 => values::U8::new(cur.u8()?).into(),
K::I16 => values::I16::new(cur.i16()?).into(),
K::U16 => values::U16::new(cur.u16()?).into(),
K::I32 => values::I32::new(cur.i32()?).into(),
K::U32 => values::U32::new(cur.u32()?).into(),
K::I64 => values::I64::new(cur.i64()?).into(),
K::U64 => values::U64::new(cur.u64()?).into(),
K::F32 => values::F32::new(cur.f32()?).into(),
K::Vector2 => values::Vector2::new(cur.vec2()?).into(),
K::Vector3 => values::Vector3::new(cur.vec3()?).into(),
K::Vector4 => values::Vector4::new(cur.vec4()?).into(),
K::Matrix44 => values::Matrix44::new(cur.mat4_row_major()?).into(),
K::Color => values::Color::new(cur.color_u8()?).into(),
K::String => read_string(cur)?.into(),
K::Hash => values::Hash::new(cur.bin_hash()?).into(),
K::WadChunkLink => values::WadChunkLink::new(cur.wad_hash()?).into(),
K::ObjectLink => values::ObjectLink::new(cur.bin_hash()?).into(),
K::Container => read_container(cur)?.into(),
K::UnorderedContainer => values::UnorderedContainer(read_container(cur)?).into(),
K::Optional => read_optional(cur)?.into(),
K::Map => read_map(cur)?.into(),
K::Struct => read_struct(cur)?.into(),
K::Embedded => values::Embedded(read_struct(cur)?).into(),
})
}
pub(crate) fn read_string(cur: &mut Cursor<'_>) -> Result<values::String, Error> {
Ok(values::String::new(cur.str_u16()?.to_owned()))
}
pub(crate) fn read_container(cur: &mut Cursor<'_>) -> Result<values::Container, Error> {
let item_kind = cur.item_kind()?;
let items = cur.sized_region(|cur| {
let count = cur.u32()?;
let mut items = Vec::with_capacity(count as usize);
for _ in 0..count {
items.push(read_value(cur, item_kind)?);
}
Ok(items)
})?;
values::Container::new(item_kind, items)
}
pub(crate) fn read_map(cur: &mut Cursor<'_>) -> Result<values::Map, Error> {
let key_kind = cur.key_kind()?;
let value_kind = cur.item_kind()?;
let entries = cur.sized_region(|cur| {
let count = cur.u32()?;
let mut entries = Vec::with_capacity(count as usize);
for _ in 0..count {
let key = read_value(cur, key_kind)?;
let value = read_value(cur, value_kind)?;
entries.push((key, value));
}
Ok(entries)
})?;
values::Map::new(key_kind, value_kind, entries)
}
pub(crate) fn read_optional(cur: &mut Cursor<'_>) -> Result<values::Optional, Error> {
let item_kind = cur.item_kind()?;
let value = match cur.bool()? {
true => Some(read_value(cur, item_kind)?),
false => None,
};
values::Optional::new(item_kind, value)
}
pub(crate) fn read_struct(cur: &mut Cursor<'_>) -> Result<values::Struct, Error> {
let class_hash = cur.bin_hash()?;
if *class_hash == 0 {
return Ok(values::Struct {
class_hash,
..Default::default()
});
}
cur.sized_region(|cur| {
let count = cur.u16()?;
Ok(values::Struct {
class_hash,
properties: read_properties(cur, count)?,
})
})
}
pub(crate) fn read_object_from<R: io::Read + io::Seek + ?Sized>(
reader: &mut R,
class_hash: BinHash,
numbering: Numbering,
) -> Result<BinObject, Error> {
let declared = reader.read_u32::<LE>()? as usize;
let mut body = Vec::new();
fill_to(reader, &mut body, declared)?;
let mut cur = Cursor::new(&body, numbering);
let object = read_object_body(&mut cur, class_hash)?;
let consumed = cur.position();
match consumed == declared {
true => Ok(object),
false => Err(Error::InvalidSize(declared as u64, consumed as u64)),
}
}
pub(crate) fn read_value_from<R: io::Read + io::Seek + ?Sized>(
reader: &mut R,
kind: Kind,
numbering: Numbering,
) -> Result<PropertyValueEnum, Error> {
read_from(reader, kind, numbering, |cur| read_value(cur, kind))
}
pub(crate) fn read_from<T, R, F>(
reader: &mut R,
kind: Kind,
numbering: Numbering,
decode: F,
) -> Result<T, Error>
where
R: io::Read + io::Seek + ?Sized,
F: FnOnce(&mut Cursor<'_>) -> Result<T, Error>,
{
let mut buf = Vec::new();
let extent = fill_value(reader, kind, numbering, &mut buf)?;
let over_read = buf.len() - extent;
if over_read > 0 {
reader.seek(io::SeekFrom::Current(-(over_read as i64)))?;
}
decode(&mut Cursor::new(&buf[..extent], numbering))
}
fn fill_value<R: io::Read + io::Seek + ?Sized>(
reader: &mut R,
kind: Kind,
numbering: Numbering,
buf: &mut Vec<u8>,
) -> Result<usize, Error> {
let mut want = kind.fixed_width().unwrap_or(PROBE);
loop {
fill_to(reader, buf, want)?;
let exhausted = buf.len() < want;
let mut cur = Cursor::new(buf.as_slice(), numbering);
match cur.walk_value(kind) {
Ok(()) => return Ok(cur.position()),
Err(error @ Error::IOError(_)) => match exhausted {
true => return Err(error),
false => want = want.saturating_mul(2),
},
Err(error) => return Err(error),
}
}
}
pub(crate) fn fill_to<R: io::Read + ?Sized>(
reader: &mut R,
buf: &mut Vec<u8>,
want: usize,
) -> Result<(), Error> {
while buf.len() < want {
let start = buf.len();
let end = start + (want - start).min(CHUNK);
buf.resize(end, 0);
let mut filled = start;
while filled < end {
match reader.read(&mut buf[filled..end]) {
Ok(0) => {
buf.truncate(filled);
return Ok(());
}
Ok(read) => filled += read,
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(error) => {
buf.truncate(filled);
return Err(error.into());
}
}
}
}
Ok(())
}