use std::io::{self, BufWriter, Write};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::{GMAFile, GmaError, HEADER, VERSION};
pub struct Builder {
name: String,
steam_id64: i64,
author: String,
description: String,
entries: Vec<GMAFile>,
}
impl Builder {
#[inline]
pub fn new(name: impl Into<String>, steam_id64: i64) -> Self {
Self::new_with_capacity(name, steam_id64, 0)
}
pub fn new_with_capacity(name: impl Into<String>, steam_id64: i64, capacity: usize) -> Self {
Self {
name: name.into(),
steam_id64,
author: "unknown".into(),
description: String::new(),
entries: Vec::with_capacity(capacity),
}
}
pub fn set_description(&mut self, desc: impl Into<String>) {
self.description = desc.into();
}
pub fn set_author(&mut self, author: impl Into<String>) {
self.author = author.into();
}
pub fn file_from_bytes(&mut self, name: impl Into<String>, bytes: Vec<u8>) {
let name = name.into();
let size = bytes.len() as i64;
self.entries.push(GMAFile {
name,
content: bytes,
size,
});
}
pub fn file_from_string(&mut self, name: impl Into<String>, content: impl Into<String>) {
self.file_from_bytes(name, content.into().into_bytes());
}
pub fn write_to<W: Write>(&self, mut w: W) -> Result<(), GmaError> {
let mut bw = BufWriter::new(&mut w);
bw.write_all(HEADER)?;
bw.write_all(&VERSION.to_le_bytes())?;
bw.write_all(&self.steam_id64.to_le_bytes())?;
let unix_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
bw.write_all(&unix_time.to_le_bytes())?;
bw.write_all(&[0u8])?;
write_cstring(&mut bw, &self.name)?;
write_cstring(&mut bw, &self.description)?;
write_cstring(&mut bw, &self.author)?;
bw.write_all(&1i32.to_le_bytes())?;
for (i, e) in self.entries.iter().enumerate() {
bw.write_all(&(i as u32 + 1).to_le_bytes())?;
write_cstring(&mut bw, &e.name)?;
bw.write_all(&(e.content.len() as i64).to_le_bytes())?;
bw.write_all(&0u32.to_le_bytes())?;
}
bw.write_all(&0u32.to_le_bytes())?;
for e in &self.entries {
bw.write_all(&e.content)?;
}
bw.write_all(&0u32.to_le_bytes())?;
bw.flush()?;
Ok(())
}
}
fn write_cstring<W: Write>(mut w: W, s: &str) -> Result<(), GmaError> {
if s.bytes().any(|b| b == 0) {
return Err(
io::Error::new(io::ErrorKind::InvalidInput, "string contains null byte").into(),
);
}
w.write_all(s.as_bytes())?;
w.write_all(&[0u8])?;
Ok(())
}