chd/compression/
cdrom.rs

1/// Common logic for CD-ROM decompression codecs.
2use crate::cdrom::{CD_FRAME_SIZE, CD_MAX_SECTOR_DATA, CD_MAX_SUBCODE_DATA, CD_SYNC_HEADER};
3use crate::compression::ecc::ErrorCorrectedSector;
4use crate::compression::lzma::LzmaCodec;
5use crate::compression::zlib::ZlibCodec;
6use crate::compression::zstd::ZstdCodec;
7use crate::compression::{
8    CodecImplementation, CompressionCodec, CompressionCodecType, DecompressResult,
9};
10use crate::error::{Error, Result};
11use crate::header::CodecType;
12use std::convert::TryFrom;
13
14/// CD-ROM wrapper decompression codec (cdlz) that uses the [LZMA codec](crate::codecs::LzmaCodec)
15/// for decompression of sector data and the [Deflate codec](crate::codecs::ZlibCodec) for
16/// decompression of subcode data.
17///
18/// ## Format Details
19/// CD-ROM compressed hunks have a layout with all compressed frame data in sequential order,
20/// followed by compressed subcode data.
21/// ```c
22/// [Header, Frame0, Frame1, ..., FrameN, Subcode0, Subcode1, ..., SubcodeN]
23/// ```
24///
25/// The slice of the input buffer from `Frame0` to `Frame1` is a single LZMA compressed stream,
26/// followed by the subcode data which is a single Deflate compressed stream.
27///
28/// The size of the header is determined by the number of 2448-byte sized frames that can fit
29/// into a hunk-sized buffer and the length of such buffer. First, the number of ECC bytes
30/// are calculated as `(frames + 7) / 8`. If the hunk size is less than 65536 (0x10000) bytes,
31/// then the length of the compressed sector data is stored as a 2 byte big-endian integer,
32/// otherwise the length is 3 bytes, stored after the number of ECC bytes in the header.
33///
34/// After decompression, the data is swizzled so that each frame is followed by its corresponding
35/// subcode data.
36/// ```c
37/// [Frame0, Subcode0, Frame1, Subcode1, ..., FrameN, SubcodeN]
38/// ```
39/// After swizzling, the following CD sync header will be written to
40/// the first 12 bytes of each frame.
41/// ```
42/// pub const CD_SYNC_HEADER: [u8; 12] = [
43///     0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
44/// ];
45/// ```
46/// The ECC data is then regenerated throughout the sector.
47///
48/// ## Buffer Restrictions
49/// Each compressed CDLZ hunk decompresses to a hunk-sized chunk. The hunk size must be a multiple of
50/// 2448, the size of each CD frame.
51/// The input buffer must contain exactly enough data to fill the hunk-sized output buffer
52/// when decompressed.
53pub type CdLzmaCodec = CdCodec<LzmaCodec, ZlibCodec>;
54
55/// CD-ROM wrapper decompression codec (cdzl) using the [Deflate codec](crate::codecs::ZlibCodec)
56/// for decompression of sector data and the [Deflate codec](crate::codecs::ZlibCodec) for
57/// decompression of subcode data.
58///
59/// ## Format Details
60/// CD-ROM compressed hunks have a layout with a header, then all compressed frame data
61/// in sequential order, followed by compressed subcode data.
62/// ```c
63/// [Header, Frame0, Frame1, ..., FrameN, Subcode0, Subcode1, ..., SubcodeN]
64/// ```
65///
66/// The slice of the input buffer from `Frame0` to `Frame1` is a single Deflate compressed stream,
67/// followed by the subcode data which is a single Deflate compressed stream.
68///
69/// The size of the header is determined by the number of 2448-byte sized frames that can fit
70/// into a hunk-sized buffer and the length of such buffer. First, the number of ECC bytes
71/// are calculated as `(frames + 7) / 8`. If the hunk size is less than 65536 (0x10000) bytes,
72/// then the length of the compressed sector data is stored as a 2 byte big-endian integer,
73/// otherwise the length is 3 bytes, stored after the number of ECC bytes in the header.
74///
75/// After decompression, the data is swizzled so that each frame is followed by its corresponding
76/// subcode data.
77///
78/// ```c
79/// [Frame0, Subcode0, Frame1, Subcode1, ..., FrameN, SubcodeN]
80/// ```
81/// After swizzling, the following CD sync header will be written to
82/// the first 12 bytes of each frame.
83/// ```
84/// pub const CD_SYNC_HEADER: [u8; 12] = [
85///     0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
86/// ];
87/// ```
88/// The ECC data is then regenerated throughout the sector.
89///
90/// ## Buffer Restrictions
91/// Each compressed CDZL hunk decompresses to a hunk-sized chunk. The hunk size must be a multiple of
92/// 2448, the size of each CD frame.
93/// The input buffer must contain exactly enough data to fill the output buffer
94/// when decompressed.
95pub type CdZlibCodec = CdCodec<ZlibCodec, ZlibCodec>;
96
97/// CD-ROM wrapper decompression codec (cdzs) using the [Zstandard codec](crate::codecs::ZstdCodec)
98/// for decompression of sector data and the [Zstandard codec](crate::codecs::ZstdCodec) for
99/// decompression of subcode data.
100///
101/// ## Format Details
102/// CD-ROM compressed hunks have a layout with a header, then all compressed frame data
103/// in sequential order, followed by compressed subcode data.
104/// ```c
105/// [Header, Frame0, Frame1, ..., FrameN, Subcode0, Subcode1, ..., SubcodeN]
106/// ```
107///
108/// The slice of the input buffer from `Frame0` to `Frame1` is a single Deflate compressed stream,
109/// followed by the subcode data which is a single Deflate compressed stream.
110///
111/// The size of the header is determined by the number of 2448-byte sized frames that can fit
112/// into a hunk-sized buffer and the length of such buffer. First, the number of ECC bytes
113/// are calculated as `(frames + 7) / 8`. If the hunk size is less than 65536 (0x10000) bytes,
114/// then the length of the compressed sector data is stored as a 2 byte big-endian integer,
115/// otherwise the length is 3 bytes, stored after the number of ECC bytes in the header.
116///
117/// After decompression, the data is swizzled so that each frame is followed by its corresponding
118/// subcode data.
119///
120/// ```c
121/// [Frame0, Subcode0, Frame1, Subcode1, ..., FrameN, SubcodeN]
122/// ```
123/// After swizzling, the following CD sync header will be written to
124/// the first 12 bytes of each frame.
125/// ```
126/// pub const CD_SYNC_HEADER: [u8; 12] = [
127///     0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
128/// ];
129/// ```
130/// The ECC data is then regenerated throughout the sector.
131///
132/// ## Buffer Restrictions
133/// Each compressed CDZS hunk decompresses to a hunk-sized chunk. The hunk size must be a multiple of
134/// 2448, the size of each CD frame.
135/// The input buffer must contain exactly enough data to fill the output buffer
136/// when decompressed.
137pub type CdZstdCodec = CdCodec<ZstdCodec, ZstdCodec>;
138
139impl CompressionCodecType for CdLzmaCodec {
140    fn codec_type(&self) -> CodecType {
141        CodecType::LzmaCdV5
142    }
143}
144
145impl CompressionCodecType for CdZlibCodec {
146    fn codec_type(&self) -> CodecType {
147        CodecType::ZLibCdV5
148    }
149}
150
151impl CompressionCodecType for CdZstdCodec {
152    fn codec_type(&self) -> CodecType {
153        CodecType::ZstdCdV5
154    }
155}
156
157impl CompressionCodec for CdZlibCodec {}
158impl CompressionCodec for CdLzmaCodec {}
159impl CompressionCodec for CdZstdCodec {}
160
161// unstable(adt_const_params): const TYPE: CodecType, but marker traits bring us
162// most of the way.
163/// CD-ROM codec wrapper.
164pub struct CdCodec<Engine: CodecImplementation, SubEngine: CodecImplementation> {
165    engine: Engine,
166    sub_engine: SubEngine,
167    buffer: Vec<u8>,
168}
169
170impl<Engine: CodecImplementation, SubEngine: CodecImplementation> CodecImplementation
171    for CdCodec<Engine, SubEngine>
172{
173    fn new(hunk_size: u32) -> Result<Self> {
174        if hunk_size % CD_FRAME_SIZE != 0 {
175            return Err(Error::CodecError);
176        }
177
178        let buffer = vec![0u8; hunk_size as usize];
179        Ok(CdCodec {
180            engine: Engine::new((hunk_size / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA)?,
181            sub_engine: SubEngine::new((hunk_size / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA)?,
182            buffer,
183        })
184    }
185
186    fn decompress(&mut self, input: &[u8], output: &mut [u8]) -> Result<DecompressResult> {
187        // https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/src/libchdr_chd.c#L647
188        let frames = output.len() / CD_FRAME_SIZE as usize;
189        let complen_bytes = if output.len() < 65536 { 2 } else { 3 };
190        let ecc_bytes = (frames + 7) / 8;
191        let header_bytes = ecc_bytes + complen_bytes;
192
193        // Extract compressed length of base
194        #[allow(clippy::identity_op)]
195        let mut sector_compressed_len: u32 =
196            (input[ecc_bytes + 0] as u32) << 8 | input[ecc_bytes + 1] as u32;
197        if complen_bytes > 2 {
198            sector_compressed_len = sector_compressed_len << 8 | input[ecc_bytes + 2] as u32;
199        }
200
201        // decode frame data
202        let frame_res = self.engine.decompress(
203            &input[header_bytes..][..sector_compressed_len as usize],
204            &mut self.buffer[..frames * CD_MAX_SECTOR_DATA as usize],
205        )?;
206
207        #[cfg(feature = "want_subcode")]
208        let sub_res = self.sub_engine.decompress(
209            &input[header_bytes + sector_compressed_len as usize..],
210            &mut self.buffer[frames * CD_MAX_SECTOR_DATA as usize..]
211                [..frames * CD_MAX_SUBCODE_DATA as usize],
212        )?;
213
214        #[cfg(not(feature = "want_subcode"))]
215        let sub_res = DecompressResult::default();
216
217        // Decompressed data has layout
218        // [Frame0, Frame1, ..., FrameN, Subcode0, Subcode1, ..., SubcodeN]
219        // We need to reassemble the data to be
220        // [Frame0, Subcode0, Frame1, Subcode1, ..., FrameN, SubcodeN]
221
222        // Reassemble frame data to expected layout.
223        for (frame_num, chunk) in self.buffer[..frames * CD_MAX_SECTOR_DATA as usize]
224            .chunks_exact(CD_MAX_SECTOR_DATA as usize)
225            .enumerate()
226        {
227            output[frame_num * CD_FRAME_SIZE as usize..][..CD_MAX_SECTOR_DATA as usize]
228                .copy_from_slice(chunk);
229        }
230
231        // Reassemble subcode data to expected layout.
232        #[cfg(feature = "want_subcode")]
233        for (frame_num, chunk) in self.buffer[frames * CD_MAX_SECTOR_DATA as usize..]
234            .chunks_exact(CD_MAX_SUBCODE_DATA as usize)
235            .enumerate()
236        {
237            output[frame_num * CD_FRAME_SIZE as usize + CD_MAX_SECTOR_DATA as usize..]
238                [..CD_MAX_SUBCODE_DATA as usize]
239                .copy_from_slice(chunk);
240        }
241
242        // Recreate ECC data
243        #[cfg(feature = "want_raw_data_sector")]
244        for frame_num in 0..frames {
245            let mut sector = <&mut [u8; CD_MAX_SECTOR_DATA as usize]>::try_from(
246                &mut output[frame_num * CD_FRAME_SIZE as usize..][..CD_MAX_SECTOR_DATA as usize],
247            )?;
248            if (input[frame_num / 8] & (1 << (frame_num % 8))) != 0 {
249                sector[0..12].copy_from_slice(&CD_SYNC_HEADER);
250                sector.generate_ecc();
251            }
252        }
253
254        Ok(frame_res + sub_res)
255    }
256}