use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use crate::Result;
use crate::block::BlockDevice;
use crate::fs::FileSource;
use crate::fs::grf::header::Header;
use crate::fs::grf::table::{self, Entry, GRF_FLAG_FILE};
use crate::fs::grf::{Grf, HEADER_SIZE};
const DATA_ALIGN: u32 = 4;
pub(super) fn add_file(
grf: &mut Grf,
dev: &mut dyn BlockDevice,
key: String,
src: FileSource,
) -> Result<()> {
let plain = read_source(src)?;
if plain.len() > (u32::MAX as usize - 1024) {
return Err(crate::Error::Unsupported(
"grf: file body larger than 4 GiB - 1 KiB".into(),
));
}
if let Some(old) = grf.entries.remove(&key) {
grf.wasted_space = grf.wasted_space.saturating_add(u64::from(old.len_aligned));
}
let compressed = crate::compression::compress(crate::compression::Algo::Zlib, &plain)?;
let len = compressed.len() as u32;
let len_aligned = len.div_ceil(DATA_ALIGN) * DATA_ALIGN;
let pos_u64 = grf.data_end - HEADER_SIZE as u64;
if pos_u64 > u32::MAX as u64 {
return Err(crate::Error::Unsupported(
"grf: archive grown past 4 GiB; repack into a new archive".into(),
));
}
let pos = pos_u64 as u32;
dev.write_at(grf.data_end, &compressed)?;
if len_aligned > len {
let pad = vec![0u8; (len_aligned - len) as usize];
dev.write_at(grf.data_end + len as u64, &pad)?;
}
grf.data_end += u64::from(len_aligned);
grf.entries.insert(
key.clone(),
Entry {
name: key,
size: plain.len() as u32,
len,
len_aligned,
pos,
flags: GRF_FLAG_FILE,
},
);
grf.dirty = true;
Ok(())
}
pub(super) fn remove(grf: &mut Grf, key: &str) -> Result<()> {
let removed = grf
.entries
.remove(key)
.ok_or_else(|| crate::Error::InvalidArgument(format!("grf: no entry at {key:?}")))?;
grf.wasted_space = grf
.wasted_space
.saturating_add(u64::from(removed.len_aligned));
grf.dirty = true;
Ok(())
}
pub(super) fn flush(grf: &mut Grf, dev: &mut dyn BlockDevice) -> Result<()> {
if !grf.dirty && !grf.fresh {
return Ok(());
}
if grf.version != 0x200 {
return Err(crate::Error::Unsupported(format!(
"grf: writer only emits v0x200; in-memory archive is v{:#x}",
grf.version
)));
}
let mut by_pos: Vec<_> = grf.entries.values().cloned().collect();
by_pos.sort_by_key(|e| e.pos);
let raw_table = table::encode_v200(&by_pos);
let compressed = crate::compression::compress(crate::compression::Algo::Zlib, &raw_table)?;
let table_abs = grf.data_end;
let mut posinfo = [0u8; 8];
posinfo[0..4].copy_from_slice(&(compressed.len() as u32).to_le_bytes());
posinfo[4..8].copy_from_slice(&(raw_table.len() as u32).to_le_bytes());
dev.write_at(table_abs, &posinfo)?;
dev.write_at(table_abs + 8, &compressed)?;
let _new_end = table_abs + 8 + compressed.len() as u64;
let table_offset = (table_abs - HEADER_SIZE as u64) as u32;
let head = Header {
encrypted_header: grf.encrypted_header,
table_offset,
seed: grf.seed,
filecount: grf.entries.len() as u32,
version: grf.version,
};
let head_bytes = head.encode();
dev.write_at(0, &head_bytes)?;
grf.table_offset = table_offset;
grf.dirty = false;
grf.fresh = false;
dev.sync()?;
Ok(())
}
fn read_source(src: FileSource) -> Result<Vec<u8>> {
let (mut reader, len) = src.open()?;
let _ = reader.seek(SeekFrom::Start(0));
let mut out = Vec::with_capacity(len as usize);
reader.read_to_end(&mut out)?;
Ok(out)
}