use std::fmt::Display;
use anyhow::{Context, Result, ensure};
use crate::file_parsers::dat::types::DatFile;
pub fn take_utf16_string(input: &[u8]) -> String {
let u16_data = input
.chunks_exact(2)
.map(|c| u16::from_le_bytes(c.try_into().unwrap()))
.take_while(|x| *x != 0)
.collect::<Vec<_>>();
String::from_utf16(&u16_data).expect("Failed to parse UTF-16 string.")
}
impl DatFile {
pub fn width(&self) -> usize {
self.rows.first().map(|row| row.len()).unwrap_or(0)
}
pub fn view_col(&self, offset: usize, width: usize) -> Result<impl Iterator<Item = &[u8]>> {
ensure!(
offset + width <= self.width(),
"Requested column out of bounds: bytes {}-{}, row width: {}",
offset,
offset + width,
self.width(),
);
let iter = self
.rows
.iter()
.map(move |row| &row[offset..offset + width]);
Ok(iter)
}
pub fn view_col_as_array(
&self,
offset: usize,
dtype_width: usize,
) -> Result<impl Iterator<Item = Result<Vec<&[u8]>>>> {
let iter = self.view_col(offset, 16)?.map(move |bytes| {
let length = u64::from_le_bytes(bytes[..8].try_into().unwrap()) as usize;
let pointer = u64::from_le_bytes(bytes[8..].try_into().unwrap()) as usize;
let start = pointer.checked_sub(8).context("underflow")?;
let end = start
.checked_add(length.checked_mul(dtype_width).context("Overflow")?)
.context("Overflow")?;
ensure!(end <= self.variable_data.len(), "Array slice oveflow");
let bytes = self.variable_data[start..end]
.chunks_exact(dtype_width)
.collect();
Ok(bytes)
});
Ok(iter)
}
pub fn view_col_as_string(
&self,
offset: usize,
) -> Result<impl Iterator<Item = Result<Option<String>>> + '_> {
let iter = self.view_col(offset, 8)?.map(move |bytes| {
let pointer = u64::from_le_bytes(bytes.try_into().unwrap()) as usize;
ensure!(
pointer >= 8 && pointer < self.variable_data.len() + 8,
"Array pointer out of bounds"
);
let string = take_utf16_string(&self.variable_data[pointer - 8..]);
let string = if string.is_empty() {
None
} else {
Some(string)
};
Ok(string)
});
Ok(iter)
}
pub fn view_col_as_array_of<'a, T, F: Fn(&[u8]) -> T + 'a>(
&'a self,
offset: usize,
dtype_width: usize,
parse_func: F,
) -> Result<impl Iterator<Item = Result<Vec<T>>> + 'a> {
let iter = self
.view_col_as_array(offset, dtype_width)?
.map(move |array| array.map(|array| array.into_iter().map(&parse_func).collect()));
Ok(iter)
}
pub fn view_col_as_array_of_strings(
&self,
offset: usize,
) -> Result<impl Iterator<Item = Result<Vec<Option<String>>>> + '_> {
let iter = self
.view_col_as_array_of(offset, 8, |bytes| {
let pointer = u64::from_le_bytes(bytes.try_into().unwrap()) as usize;
ensure!(pointer >= 8, "String pointer underflow");
ensure!(
pointer - 8 < self.variable_data.len(),
"String pointer overflow"
);
let string = take_utf16_string(&self.variable_data[pointer - 8..]);
let string = if string.is_empty() {
None
} else {
Some(string)
};
Ok(string)
})?
.map(|x| x?.into_iter().collect::<Result<Vec<_>>>());
Ok(iter)
}
}
impl Display for DatFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for row in self.rows.iter() {
let chars = row
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(" ");
writeln!(f, "{}", chars)?;
}
Ok(())
}
}