1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
mod read;
mod write;

use std::io::{Read, Write};

use flate2::read::ZlibDecoder;
use flate2::write::ZlibEncoder;
use flate2::Compression;

pub use self::read::DcxDecoder;
pub use self::write::{DcxBuilder, DcxEncoder, DcxWriter};

#[derive(Debug, PartialEq, Eq)]
pub struct DcxHeader {
    pub(crate) version: u32,
    pub(crate) compression_parameters: CompressionParameters,
    pub(crate) compressed_sizes: CompressedSizes,
    pub(crate) chunk_info: ChunkInfo,
}

#[derive(Debug, PartialEq, Eq)]
pub struct ChunkInfo {}

#[derive(Debug, PartialEq, Eq)]
pub struct CompressedSizes {
    pub(crate) uncompressed_size: u32,
    pub(crate) compressed_size: u32,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CompressionParameters {
    Deflate { compression_level: u8 },
    Kraken {},
    Edge,
}

impl CompressionParameters {
    pub fn deflate_fast() -> Self {
        CompressionParameters::Deflate {
            compression_level: 9,
        }
    }

    pub fn create_decoder<R: Read>(&self, inner: R) -> DcxDecoder<R> {
        match self {
            CompressionParameters::Deflate { .. } => DcxDecoder::Deflate(ZlibDecoder::new(inner)),
            _ => unimplemented!("{:#?} decompression is not yet supported", &self),
        }
    }

    pub fn create_encoder<W: Write>(&self, inner: W) -> DcxEncoder<W> {
        match self {
            CompressionParameters::Deflate { compression_level } => DcxEncoder::Deflate(
                ZlibEncoder::new(inner, Compression::new(*compression_level as u32)),
            ),
            _ => unimplemented!("{:#?} compression is not yet supported", &self),
        }
    }

    pub fn id(&self) -> &'static [u8] {
        match self {
            Self::Deflate { .. } => b"DFLT",
            Self::Kraken {} => b"KRAK",
            Self::Edge => b"EDGE",
        }
    }
}

#[cfg(test)]
mod test {
    use std::{fs::File, io::Cursor};

    use pretty_assertions::assert_eq;

    use super::*;
    #[test]
    fn roundtrip() {
        // TODO: helpers for reading test fixtures and roundtripping decoders/encoders
        let path = format!(
            "{}/test-data/formats/dcx/o000499.objbnd.dcx",
            env!("CARGO_MANIFEST_DIR")
        );
        let mut file = File::open(path).unwrap();
        let mut encoded_file = Vec::new();
        file.read_to_end(&mut encoded_file).unwrap();

        let (header, mut reader) = DcxDecoder::new(Cursor::new(&encoded_file[..])).unwrap();
        let mut decoded_data =
            Vec::with_capacity(header.compressed_sizes.uncompressed_size as usize);
        reader.read_to_end(&mut decoded_data).unwrap();

        let mut reencoded_file =
            Vec::with_capacity(header.compressed_sizes.compressed_size as usize);
        let mut writer = DcxBuilder::new(header.version)
            .write(
                Cursor::new(&mut reencoded_file),
                header.compression_parameters,
            )
            .unwrap();

        writer.write_all(&decoded_data).unwrap();
        writer.finish().unwrap();

        let mut roundtripped_data =
            Vec::with_capacity(header.compressed_sizes.uncompressed_size as usize);
        let (_, mut reencoded_reader) = DcxDecoder::new(Cursor::new(&reencoded_file[..])).unwrap();
        reencoded_reader
            .read_to_end(&mut roundtripped_data)
            .unwrap();

        assert_eq!(decoded_data, roundtripped_data);
    }
}