use crate::{
fs::{File, FileToLsdSngError, Filesystem},
name::{self, Name},
serde::{CompressBlockError, End, compress_block, decompress_block},
song::{self, SongMemory},
};
use std::{
io::{self, Cursor, Read, Seek, SeekFrom, Write},
path::Path,
slice,
};
use thiserror::Error;
#[derive(Clone)]
pub struct LsdSng {
name: Name<8>,
version: u8,
blocks: Vec<u8>,
}
impl LsdSng {
pub(crate) fn new(name: Name<8>, version: u8, blocks: Vec<u8>) -> Self {
Self {
name,
version,
blocks,
}
}
pub fn from_song(
name: Name<8>,
version: u8,
song: &SongMemory,
) -> Result<Self, CompressBlockError> {
let mut blocks = Vec::new();
let mut reader = Cursor::new(song.as_slice());
loop {
let mut block = [0; Filesystem::BLOCK_LEN];
let end = compress_block(&mut reader, Cursor::new(block.as_mut_slice()), || {
Some(blocks.len() as u8)
})?;
blocks.push(block);
if end == End::EndOfFile {
break;
}
}
Ok(Self::new(
name,
version,
blocks.iter().flatten().copied().collect(),
))
}
pub fn from_reader<R>(mut reader: R) -> Result<Self, FromReaderError>
where
R: Read,
{
let name = {
let mut bytes = [0; 8];
reader.read_exact(&mut bytes)?;
Name::from_bytes(bytes.as_mut_slice())?
};
let mut version = 0;
reader.read_exact(slice::from_mut(&mut version))?;
let mut blocks = Vec::new();
reader.read_to_end(&mut blocks)?;
Ok(LsdSng {
name,
version,
blocks,
})
}
pub fn from_path<P>(path: P) -> Result<Self, FromPathError>
where
P: AsRef<Path>,
{
let file = std::fs::File::open(path)?;
Ok(Self::from_reader(file)?)
}
pub fn to_writer<W>(&self, mut writer: W) -> Result<(), io::Error>
where
W: Write,
{
writer.write_all(self.name.bytes())?;
writer.write_all(slice::from_ref(&self.version))?;
writer.write_all(&self.blocks)?;
Ok(())
}
pub fn to_path<P>(&self, path: P) -> Result<(), io::Error>
where
P: AsRef<Path>,
{
self.to_writer(std::fs::File::create(path)?)
}
}
impl File for LsdSng {
fn name(&self) -> Result<Name<8>, name::FromBytesError> {
Ok(self.name.clone())
}
fn version(&self) -> u8 {
self.version
}
fn decompress(&self) -> Result<SongMemory, song::FromReaderError> {
let mut reader = Cursor::new(&self.blocks);
let mut memory = [0; SongMemory::LEN];
let mut writer = Cursor::new(memory.as_mut_slice());
let mut block = 0;
while decompress_block(&mut reader, &mut writer)? != End::EndOfFile {
block += 1;
reader.seek(SeekFrom::Start((block * Filesystem::BLOCK_LEN) as u64))?;
}
assert_eq!(writer.stream_position()?, SongMemory::LEN as u64);
SongMemory::from_reader(Cursor::new(memory))
}
fn lsdsng(&self) -> Result<LsdSng, FileToLsdSngError> {
Ok(self.clone())
}
}
#[derive(Debug, Error)]
pub enum FromReaderError {
#[error("Something failed with I/O")]
Read(#[from] io::Error),
#[error("Reading the name failed")]
Name(#[from] name::FromBytesError),
}
#[derive(Debug, Error)]
pub enum FromPathError {
#[error("Could not open the file for reading")]
FileOpen(#[from] io::Error),
#[error("Reading the LsdSng from file failed")]
Read(#[from] FromReaderError),
}
#[cfg(test)]
mod tests {
use super::*;
use std::{io::Cursor, str::FromStr};
#[test]
fn empty() {
let source = include_bytes!("../test/92L_empty.lsdsng");
let lsdsng = LsdSng::from_reader(Cursor::new(source)).unwrap();
assert_eq!(lsdsng.name, Name::<8>::from_str("EMPTY").unwrap());
assert_eq!(lsdsng.version, 0);
let song = lsdsng.decompress().unwrap();
assert_eq!(song.format_version(), 0x16);
let mut dest = vec![0; source.len()];
lsdsng.to_writer(Cursor::new(&mut dest)).unwrap();
assert_eq!(&dest, source);
}
}