mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/data/mod.rs')
-rw-r--r--src/data/mod.rs530
1 files changed, 530 insertions, 0 deletions
diff --git a/src/data/mod.rs b/src/data/mod.rs
new file mode 100644
index 0000000..0583ea4
--- /dev/null
+++ b/src/data/mod.rs
@@ -0,0 +1,530 @@
+//! all the IO
+use flate2::{
+ Compress, CompressError as CError, Compression, Decompress, DecompressError as DError,
+ FlushCompress, FlushDecompress, Status,
+};
+use std::collections::HashMap;
+use std::error::Error;
+use std::fmt;
+use std::str::Utf8Error;
+use thiserror::Error;
+
+pub(crate) mod autotile;
+mod base64;
+pub mod command;
+pub mod dynamic;
+pub mod map;
+pub mod planet;
+pub mod renderer;
+pub mod schematic;
+pub mod sector;
+pub mod weather;
+
+#[derive(Debug)]
+pub struct DataRead<'d> {
+ pub(crate) data: &'d [u8],
+ // used with read_chunk
+ read: usize,
+}
+
+impl fmt::Display for DataRead<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", String::from_utf8_lossy(self.data))
+ }
+}
+
+macro_rules! make_read {
+ ($name:ident, $type:ty) => {
+ pub fn $name(&mut self) -> Result<$type, ReadError> {
+ const LEN: usize = std::mem::size_of::<$type>();
+ if self.data.len() < LEN {
+ return Err(ReadError::Underflow {
+ need: LEN,
+ have: self.data.len(),
+ });
+ }
+ let mut output = [0u8; LEN];
+ output.copy_from_slice(&self.data[..LEN]);
+ self.data = &self.data[LEN..];
+ self.read += LEN;
+ Ok(<$type>::from_be_bytes(output))
+ }
+ };
+}
+
+impl<'d> DataRead<'d> {
+ #[must_use]
+ pub const fn new(data: &'d [u8]) -> Self {
+ Self { data, read: 0 }
+ }
+
+ pub fn read_bool(&mut self) -> Result<bool, ReadError> {
+ Ok(self.read_u8()? != 0)
+ }
+
+ make_read!(read_u8, u8);
+ make_read!(read_i8, i8);
+ make_read!(read_u16, u16);
+ make_read!(read_i16, i16);
+ make_read!(read_u32, u32);
+ make_read!(read_i32, i32);
+ make_read!(read_f32, f32);
+ make_read!(read_u64, u64);
+ make_read!(read_i64, i64);
+ make_read!(read_f64, f64);
+
+ pub fn read_utf(&mut self) -> Result<&'d str, ReadError> {
+ if self.data.len() < 2 {
+ return Err(ReadError::Underflow {
+ need: 2,
+ have: self.data.len(),
+ });
+ }
+ let len = self.read_u16()?;
+ let end = len as usize;
+ if self.data.len() < end {
+ return Err(ReadError::Underflow {
+ need: end,
+ have: self.data.len(),
+ });
+ }
+ let result = std::str::from_utf8(&self.data[..end])?;
+ self.data = &self.data[end..];
+ self.read += end;
+ Ok(result)
+ }
+
+ pub fn read_bytes(&mut self, dst: &mut [u8]) -> Result<(), ReadError> {
+ if self.data.len() < dst.len() {
+ return Err(ReadError::Underflow {
+ need: dst.len(),
+ have: self.data.len(),
+ });
+ }
+ dst.copy_from_slice(&self.data[..dst.len()]);
+ self.data = &self.data[dst.len()..];
+ self.read += dst.len();
+ Ok(())
+ }
+
+ pub fn skip(&mut self, n: usize) -> Result<(), ReadError> {
+ if self.data.len() < n {
+ return Err(ReadError::Underflow {
+ need: n,
+ have: self.data.len(),
+ });
+ }
+ self.data = &self.data[n..];
+ self.read += n;
+ Ok(())
+ }
+
+ pub fn skip_chunk(&mut self) -> Result<usize, ReadError> {
+ let len = self.read_u32()? as usize;
+ self.skip(len)?;
+ Ok(len)
+ }
+
+ pub fn read_chunk<E>(
+ &mut self,
+ big: bool,
+ f: impl FnOnce(&mut DataRead) -> Result<(), E>,
+ ) -> Result<(), E>
+ where
+ E: Error + From<ReadError>,
+ {
+ let len = if big {
+ self.read_u32()? as usize
+ } else {
+ self.read_u16()? as usize
+ };
+ self.read = 0;
+ let r = f(self);
+ match r {
+ Err(e) => {
+ // skip this chunk
+ assert!(
+ len >= self.read,
+ "overread; supposed to read {len}; read {}",
+ self.read
+ );
+ let n = len - self.read;
+ if n != 0 {
+ #[cfg(debug_assertions)]
+ println!(
+ "supposed to read {len}; read {} - skipping excess",
+ self.read
+ );
+ self.skip(n)?;
+ };
+ Err(e)
+ }
+ Ok(_) => Ok(()),
+ }
+ }
+
+ pub fn read_vec(&mut self, dst: &mut Vec<u8>, len: usize) -> Result<(), ReadError> {
+ if self.data.len() < len {
+ return Err(ReadError::Underflow {
+ need: len,
+ have: self.data.len(),
+ });
+ }
+ dst.extend_from_slice(&self.data[..len]);
+ self.data = &self.data[len..];
+ self.read += len;
+ Ok(())
+ }
+
+ pub fn read_map(&mut self, dst: &mut HashMap<String, String>) -> Result<(), ReadError> {
+ let n = self.read_u8()?;
+ for _ in 0..n {
+ let key = self.read_utf()?;
+ let value = self.read_utf()?;
+ dst.insert(key.to_owned(), value.to_owned());
+ }
+ Ok(())
+ }
+
+ pub fn deflate(&mut self) -> Result<Vec<u8>, DecompressError> {
+ let mut dec = Decompress::new(true);
+ let mut raw = Vec::<u8>::new();
+ raw.reserve(1024);
+ loop {
+ let t_in = dec.total_in();
+ let t_out = dec.total_out();
+ let res = dec.decompress_vec(self.data, &mut raw, FlushDecompress::Finish)?;
+ if dec.total_in() > t_in {
+ // we have to advance input every time, decompress_vec only knows the output position
+ self.data = &self.data[(dec.total_in() - t_in) as usize..];
+ }
+ match res {
+ // there's no more input (and the flush mode says so), we need to reserve additional space
+ Status::Ok | Status::BufError => (),
+ // input was already at the end, so this is referring to the output
+ Status::StreamEnd => break,
+ }
+ if dec.total_in() == t_in && dec.total_out() == t_out {
+ // protect against looping forever
+ return Err(DecompressError::DecompressStall);
+ }
+ raw.reserve(1024);
+ }
+ assert_eq!(dec.total_out() as usize, raw.len());
+ self.read = 0;
+ Ok(raw)
+ }
+}
+
+#[derive(Debug, Error)]
+pub enum DecompressError {
+ #[error("zlib decompression failed")]
+ Decompress(#[from] DError),
+ #[error("decompressor stalled before completion")]
+ DecompressStall,
+}
+
+#[derive(Debug, Error)]
+pub enum ReadError {
+ #[error("buffer underflow (expected {need} but got {have})")]
+ Underflow { need: usize, have: usize },
+ #[error("expected {0}")]
+ Expected(&'static str),
+ #[error("malformed utf8 in string")]
+ Utf8 {
+ #[from]
+ source: Utf8Error,
+ },
+}
+
+impl PartialEq for ReadError {
+ fn eq(&self, _: &Self) -> bool {
+ false
+ }
+}
+
+enum WriteBuff<'d> {
+ // unlike the DataRead want to access the written region after
+ Ref { raw: &'d mut [u8], pos: usize },
+ Vec(Vec<u8>),
+}
+
+impl<'d> WriteBuff<'d> {
+ fn check_capacity(&self, need: usize) -> Result<(), WriteError> {
+ match self {
+ Self::Ref { raw, pos } if raw.len() - pos < need => Err(WriteError::Overflow {
+ need,
+ have: raw.len() - pos,
+ }),
+ _ => Ok(()),
+ }
+ }
+
+ fn write(&mut self, data: &[u8]) {
+ match self {
+ Self::Ref { raw, pos } => {
+ let end = *pos + data.len();
+ raw[*pos..end].copy_from_slice(data);
+ *pos += data.len();
+ }
+ Self::Vec(v) => v.extend_from_slice(data),
+ }
+ }
+}
+
+pub struct DataWrite<'d> {
+ data: WriteBuff<'d>,
+}
+
+macro_rules! make_write {
+ ($name:ident, $type:ty) => {
+ pub fn $name(&mut self, val: $type) -> Result<(), WriteError> {
+ const LEN: usize = std::mem::size_of::<$type>();
+ self.data.check_capacity(LEN)?;
+ self.data.write(&<$type>::to_be_bytes(val));
+ Ok(())
+ }
+ };
+}
+
+impl<'d> DataWrite<'d> {
+ pub fn write_bool(&mut self, val: bool) -> Result<(), WriteError> {
+ self.write_u8(u8::from(val))
+ }
+
+ make_write!(write_u8, u8);
+ make_write!(write_i8, i8);
+ make_write!(write_u16, u16);
+ make_write!(write_i16, i16);
+ make_write!(write_u32, u32);
+ make_write!(write_i32, i32);
+ make_write!(write_f32, f32);
+ make_write!(write_u64, u64);
+ make_write!(write_i64, i64);
+ make_write!(write_f64, f64);
+
+ pub fn write_utf(&mut self, val: &str) -> Result<(), WriteError> {
+ if val.len() > u16::MAX as usize {
+ return Err(WriteError::TooLong { len: val.len() });
+ }
+ self.data.check_capacity(2 + val.len())?;
+ self.data.write(&u16::to_be_bytes(val.len() as u16));
+ self.data.write(val.as_bytes());
+ Ok(())
+ }
+
+ pub fn write_bytes(&mut self, val: &[u8]) -> Result<(), WriteError> {
+ self.data.check_capacity(val.len())?;
+ self.data.write(val);
+ Ok(())
+ }
+
+ #[must_use]
+ pub const fn is_owned(&self) -> bool {
+ matches!(self.data, WriteBuff::Vec(..))
+ }
+
+ #[must_use]
+ pub fn get_written(&self) -> &[u8] {
+ match &self.data {
+ WriteBuff::Ref { raw, pos } => &raw[..*pos],
+ WriteBuff::Vec(v) => v,
+ }
+ }
+
+ /// eat this datawrite
+ ///
+ /// panics if ref write buffer
+ #[must_use]
+ pub fn consume(self) -> Vec<u8> {
+ match self.data {
+ WriteBuff::Vec(v) => v,
+ WriteBuff::Ref { .. } => unreachable!(),
+ }
+ }
+
+ pub fn inflate(self, to: &mut DataWrite) -> Result<(), CompressError> {
+ // compress into the provided buffer
+ let WriteBuff::Vec(raw) = self.data else {
+ unreachable!("write buffer not owned")
+ };
+ let mut comp = Compress::new(Compression::default(), true);
+ // compress the immediate buffer into a temp buffer to copy it to buff? no thanks
+ match to.data {
+ WriteBuff::Ref {
+ raw: ref mut dst,
+ ref mut pos,
+ } => {
+ match comp.compress(&raw, &mut dst[*pos..], FlushCompress::Finish)? {
+ // there's no more input (and the flush mode says so), but we can't resize the output
+ Status::Ok | Status::BufError => {
+ return Err(CompressError::CompressEof(
+ raw.len() - comp.total_in() as usize,
+ ))
+ }
+ Status::StreamEnd => (),
+ }
+ }
+ WriteBuff::Vec(ref mut dst) => {
+ let mut input = raw.as_ref();
+ dst.reserve(1024);
+ loop {
+ let t_in = comp.total_in();
+ let t_out = comp.total_out();
+ let res = comp.compress_vec(input, dst, FlushCompress::Finish)?;
+ if comp.total_in() > t_in {
+ // we have to advance input every time, compress_vec only knows the output position
+ input = &input[(comp.total_in() - t_in) as usize..];
+ }
+ match res {
+ // there's no more input (and the flush mode says so), we need to reserve additional space
+ Status::Ok | Status::BufError => (),
+ // input was already at the end, so this is referring to the output
+ Status::StreamEnd => break,
+ }
+ if comp.total_in() == t_in && comp.total_out() == t_out {
+ // protect against looping forever
+ return Err(CompressError::CompressStall);
+ }
+ dst.reserve(1024);
+ }
+ }
+ }
+ assert_eq!(comp.total_in() as usize, raw.len());
+ Ok(())
+ }
+}
+
+impl Default for DataWrite<'static> {
+ fn default() -> Self {
+ Self {
+ data: WriteBuff::Vec(Vec::new()),
+ }
+ }
+}
+
+#[derive(Debug, Error)]
+pub enum CompressError {
+ #[error(transparent)]
+ Compress(#[from] CError),
+ #[error("compression overflow with {0} bytes of input remaining")]
+ CompressEof(usize),
+ #[error("compressor stalled before completion")]
+ CompressStall,
+}
+
+#[derive(Debug, Error)]
+pub enum WriteError {
+ #[error("buffer overflow (expected {need} but got {have})")]
+ Overflow { need: usize, have: usize },
+ #[error("string too long ({len} bytes of {})", u16::MAX)]
+ TooLong { len: usize },
+}
+
+impl PartialEq for WriteError {
+ fn eq(&self, _: &Self) -> bool {
+ false
+ }
+}
+
+impl<'d> From<&'d mut [u8]> for DataWrite<'d> {
+ fn from(value: &'d mut [u8]) -> Self {
+ Self {
+ data: WriteBuff::Ref { raw: value, pos: 0 },
+ }
+ }
+}
+
+impl From<Vec<u8>> for DataWrite<'static> {
+ fn from(value: Vec<u8>) -> Self {
+ Self {
+ data: WriteBuff::Vec(value),
+ }
+ }
+}
+
+impl<'d> TryFrom<DataWrite<'d>> for Vec<u8> {
+ type Error = ();
+
+ fn try_from(value: DataWrite<'d>) -> Result<Self, Self::Error> {
+ match value.data {
+ WriteBuff::Vec(v) => Ok(v),
+ WriteBuff::Ref { .. } => Err(()),
+ }
+ }
+}
+/// basic serialization/deserialization functions
+pub trait Serializer<D> {
+ type ReadError;
+ type WriteError;
+
+ fn deserialize(&mut self, buff: &mut DataRead<'_>) -> Result<D, Self::ReadError>;
+
+ fn serialize(&mut self, buff: &mut DataWrite<'_>, data: &D) -> Result<(), Self::WriteError>;
+}
+
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub struct GridPos(pub usize, pub usize);
+
+impl From<u32> for GridPos {
+ fn from(value: u32) -> Self {
+ GridPos((value >> 16) as u16 as usize, value as u16 as usize)
+ }
+}
+
+impl From<GridPos> for u32 {
+ /// ```
+ /// # use mindus::data::GridPos;
+ /// assert_eq!(GridPos::from(u32::from(GridPos(1000, 5))), GridPos(1000, 5));
+ /// ```
+ fn from(value: GridPos) -> Self {
+ ((value.0 << 16) | value.1) as u32
+ }
+}
+
+impl fmt::Debug for GridPos {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "({0}, {1})", self.0, self.1)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn read() {
+ let mut read = DataRead::new("Thé qûick ઉrown fox 🦘 over\0\rthe lazy dog.".as_bytes());
+ assert_eq!(read.read_u8(), Ok(84));
+ assert_eq!(read.read_i8(), Ok(104));
+ assert_eq!(read.read_i8(), Ok(-61));
+ assert_eq!(read.read_u16(), Ok(43296));
+ assert_eq!(read.read_i16(), Ok(29123));
+ assert_eq!(read.read_i16(), Ok(-17559));
+ assert_eq!(read.read_i32(), Ok(1_667_965_152));
+ assert_eq!(read.read_i32(), Ok(-1_433_832_849));
+ assert_eq!(read.read_i64(), Ok(8_605_851_562_280_493_296));
+ assert_eq!(read.read_i64(), Ok(-6_942_694_510_468_635_278));
+ assert_eq!(read.read_utf(), Ok("the lazy dog."));
+ }
+
+ #[test]
+ fn write() {
+ let mut write = DataWrite::default();
+ assert_eq!(write.write_u8(84), Ok(()));
+ assert_eq!(write.write_i8(104), Ok(()));
+ assert_eq!(write.write_i8(-61), Ok(()));
+ assert_eq!(write.write_u16(43296), Ok(()));
+ assert_eq!(write.write_i16(29123), Ok(()));
+ assert_eq!(write.write_i16(-17559), Ok(()));
+ assert_eq!(write.write_i32(1_667_965_152), Ok(()));
+ assert_eq!(write.write_i32(-1_433_832_849), Ok(()));
+ assert_eq!(write.write_i64(8_605_851_562_280_493_296), Ok(()));
+ assert_eq!(write.write_i64(-6_942_694_510_468_635_278), Ok(()));
+ assert_eq!(write.write_utf("the lazy dog."), Ok(()));
+ assert_eq!(
+ write.get_written(),
+ "Thé qûick ઉrown fox 🦘 over\0\rthe lazy dog.".as_bytes()
+ );
+ }
+}