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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
/// Common logic for CD-ROM decompression codecs.
use crate::cdrom::{CD_FRAME_SIZE, CD_MAX_SECTOR_DATA, CD_MAX_SUBCODE_DATA, CD_SYNC_HEADER};
use crate::compression::ecc::ErrorCorrectedSector;
use crate::compression::lzma::LzmaCodec;
use crate::compression::zlib::ZlibCodec;
use crate::compression::zstd::ZstdCodec;
use crate::compression::{
CodecImplementation, CompressionCodec, CompressionCodecType, DecompressResult,
};
use crate::error::{Error, Result};
use crate::header::CodecType;
use std::convert::TryFrom;
/// CD-ROM wrapper decompression codec (cdlz) that uses the [LZMA codec](crate::codecs::LzmaCodec)
/// for decompression of sector data and the [Deflate codec](crate::codecs::ZlibCodec) for
/// decompression of subcode data.
///
/// ## Format Details
/// CD-ROM compressed hunks have a layout with all compressed frame data in sequential order,
/// followed by compressed subcode data.
/// ```c
/// [Header, Frame0, Frame1, ..., FrameN, Subcode0, Subcode1, ..., SubcodeN]
/// ```
///
/// The slice of the input buffer from `Frame0` to `Frame1` is a single LZMA compressed stream,
/// followed by the subcode data which is a single Deflate compressed stream.
///
/// The size of the header is determined by the number of 2448-byte sized frames that can fit
/// into a hunk-sized buffer and the length of such buffer. First, the number of ECC bytes
/// are calculated as `(frames + 7) / 8`. If the hunk size is less than 65536 (0x10000) bytes,
/// then the length of the compressed sector data is stored as a 2 byte big-endian integer,
/// otherwise the length is 3 bytes, stored after the number of ECC bytes in the header.
///
/// After decompression, the data is swizzled so that each frame is followed by its corresponding
/// subcode data.
/// ```c
/// [Frame0, Subcode0, Frame1, Subcode1, ..., FrameN, SubcodeN]
/// ```
/// After swizzling, the following CD sync header will be written to
/// the first 12 bytes of each frame.
/// ```
/// pub const CD_SYNC_HEADER: [u8; 12] = [
/// 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
/// ];
/// ```
/// The ECC data is then regenerated throughout the sector.
///
/// ## Buffer Restrictions
/// Each compressed CDLZ hunk decompresses to a hunk-sized chunk. The hunk size must be a multiple of
/// 2448, the size of each CD frame.
/// The input buffer must contain exactly enough data to fill the hunk-sized output buffer
/// when decompressed.
pub type CdLzmaCodec = CdCodec<LzmaCodec, ZlibCodec>;
/// CD-ROM wrapper decompression codec (cdzl) using the [Deflate codec](crate::codecs::ZlibCodec)
/// for decompression of sector data and the [Deflate codec](crate::codecs::ZlibCodec) for
/// decompression of subcode data.
///
/// ## Format Details
/// CD-ROM compressed hunks have a layout with a header, then all compressed frame data
/// in sequential order, followed by compressed subcode data.
/// ```c
/// [Header, Frame0, Frame1, ..., FrameN, Subcode0, Subcode1, ..., SubcodeN]
/// ```
///
/// The slice of the input buffer from `Frame0` to `Frame1` is a single Deflate compressed stream,
/// followed by the subcode data which is a single Deflate compressed stream.
///
/// The size of the header is determined by the number of 2448-byte sized frames that can fit
/// into a hunk-sized buffer and the length of such buffer. First, the number of ECC bytes
/// are calculated as `(frames + 7) / 8`. If the hunk size is less than 65536 (0x10000) bytes,
/// then the length of the compressed sector data is stored as a 2 byte big-endian integer,
/// otherwise the length is 3 bytes, stored after the number of ECC bytes in the header.
///
/// After decompression, the data is swizzled so that each frame is followed by its corresponding
/// subcode data.
///
/// ```c
/// [Frame0, Subcode0, Frame1, Subcode1, ..., FrameN, SubcodeN]
/// ```
/// After swizzling, the following CD sync header will be written to
/// the first 12 bytes of each frame.
/// ```
/// pub const CD_SYNC_HEADER: [u8; 12] = [
/// 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
/// ];
/// ```
/// The ECC data is then regenerated throughout the sector.
///
/// ## Buffer Restrictions
/// Each compressed CDZL hunk decompresses to a hunk-sized chunk. The hunk size must be a multiple of
/// 2448, the size of each CD frame.
/// The input buffer must contain exactly enough data to fill the output buffer
/// when decompressed.
pub type CdZlibCodec = CdCodec<ZlibCodec, ZlibCodec>;
/// CD-ROM wrapper decompression codec (cdzs) using the [Zstandard codec](crate::codecs::ZstdCodec)
/// for decompression of sector data and the [Zstandard codec](crate::codecs::ZstdCodec) for
/// decompression of subcode data.
///
/// ## Format Details
/// CD-ROM compressed hunks have a layout with a header, then all compressed frame data
/// in sequential order, followed by compressed subcode data.
/// ```c
/// [Header, Frame0, Frame1, ..., FrameN, Subcode0, Subcode1, ..., SubcodeN]
/// ```
///
/// The slice of the input buffer from `Frame0` to `Frame1` is a single Deflate compressed stream,
/// followed by the subcode data which is a single Deflate compressed stream.
///
/// The size of the header is determined by the number of 2448-byte sized frames that can fit
/// into a hunk-sized buffer and the length of such buffer. First, the number of ECC bytes
/// are calculated as `(frames + 7) / 8`. If the hunk size is less than 65536 (0x10000) bytes,
/// then the length of the compressed sector data is stored as a 2 byte big-endian integer,
/// otherwise the length is 3 bytes, stored after the number of ECC bytes in the header.
///
/// After decompression, the data is swizzled so that each frame is followed by its corresponding
/// subcode data.
///
/// ```c
/// [Frame0, Subcode0, Frame1, Subcode1, ..., FrameN, SubcodeN]
/// ```
/// After swizzling, the following CD sync header will be written to
/// the first 12 bytes of each frame.
/// ```
/// pub const CD_SYNC_HEADER: [u8; 12] = [
/// 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
/// ];
/// ```
/// The ECC data is then regenerated throughout the sector.
///
/// ## Buffer Restrictions
/// Each compressed CDZS hunk decompresses to a hunk-sized chunk. The hunk size must be a multiple of
/// 2448, the size of each CD frame.
/// The input buffer must contain exactly enough data to fill the output buffer
/// when decompressed.
pub type CdZstdCodec = CdCodec<ZstdCodec, ZstdCodec>;
impl CompressionCodecType for CdLzmaCodec {
fn codec_type(&self) -> CodecType {
CodecType::LzmaCdV5
}
}
impl CompressionCodecType for CdZlibCodec {
fn codec_type(&self) -> CodecType {
CodecType::ZLibCdV5
}
}
impl CompressionCodecType for CdZstdCodec {
fn codec_type(&self) -> CodecType {
CodecType::ZstdCdV5
}
}
impl CompressionCodec for CdZlibCodec {}
impl CompressionCodec for CdLzmaCodec {}
impl CompressionCodec for CdZstdCodec {}
// unstable(adt_const_params): const TYPE: CodecType, but marker traits bring us
// most of the way.
/// CD-ROM codec wrapper.
pub struct CdCodec<Engine: CodecImplementation, SubEngine: CodecImplementation> {
engine: Engine,
sub_engine: SubEngine,
buffer: Vec<u8>,
}
impl<Engine: CodecImplementation, SubEngine: CodecImplementation> CodecImplementation
for CdCodec<Engine, SubEngine>
{
fn new(hunk_size: u32) -> Result<Self> {
if hunk_size % CD_FRAME_SIZE != 0 {
return Err(Error::CodecError);
}
let buffer = vec![0u8; hunk_size as usize];
Ok(CdCodec {
engine: Engine::new((hunk_size / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA)?,
sub_engine: SubEngine::new((hunk_size / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA)?,
buffer,
})
}
fn decompress(&mut self, input: &[u8], output: &mut [u8]) -> Result<DecompressResult> {
// https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/src/libchdr_chd.c#L647
let frames = output.len() / CD_FRAME_SIZE as usize;
let complen_bytes = if output.len() < 65536 { 2 } else { 3 };
let ecc_bytes = (frames + 7) / 8;
let header_bytes = ecc_bytes + complen_bytes;
// Extract compressed length of base
#[allow(clippy::identity_op)]
let mut sector_compressed_len: u32 =
(input[ecc_bytes + 0] as u32) << 8 | input[ecc_bytes + 1] as u32;
if complen_bytes > 2 {
sector_compressed_len = sector_compressed_len << 8 | input[ecc_bytes + 2] as u32;
}
// decode frame data
let frame_res = self.engine.decompress(
&input[header_bytes..][..sector_compressed_len as usize],
&mut self.buffer[..frames * CD_MAX_SECTOR_DATA as usize],
)?;
#[cfg(feature = "want_subcode")]
let sub_res = self.sub_engine.decompress(
&input[header_bytes + sector_compressed_len as usize..],
&mut self.buffer[frames * CD_MAX_SECTOR_DATA as usize..]
[..frames * CD_MAX_SUBCODE_DATA as usize],
)?;
#[cfg(not(feature = "want_subcode"))]
let sub_res = DecompressResult::default();
// Decompressed data has layout
// [Frame0, Frame1, ..., FrameN, Subcode0, Subcode1, ..., SubcodeN]
// We need to reassemble the data to be
// [Frame0, Subcode0, Frame1, Subcode1, ..., FrameN, SubcodeN]
// Reassemble frame data to expected layout.
for (frame_num, chunk) in self.buffer[..frames * CD_MAX_SECTOR_DATA as usize]
.chunks_exact(CD_MAX_SECTOR_DATA as usize)
.enumerate()
{
output[frame_num * CD_FRAME_SIZE as usize..][..CD_MAX_SECTOR_DATA as usize]
.copy_from_slice(chunk);
}
// Reassemble subcode data to expected layout.
#[cfg(feature = "want_subcode")]
for (frame_num, chunk) in self.buffer[frames * CD_MAX_SECTOR_DATA as usize..]
.chunks_exact(CD_MAX_SUBCODE_DATA as usize)
.enumerate()
{
output[frame_num * CD_FRAME_SIZE as usize + CD_MAX_SECTOR_DATA as usize..]
[..CD_MAX_SUBCODE_DATA as usize]
.copy_from_slice(chunk);
}
// Recreate ECC data
#[cfg(feature = "want_raw_data_sector")]
for frame_num in 0..frames {
let mut sector = <&mut [u8; CD_MAX_SECTOR_DATA as usize]>::try_from(
&mut output[frame_num * CD_FRAME_SIZE as usize..][..CD_MAX_SECTOR_DATA as usize],
)?;
if (input[frame_num / 8] & (1 << (frame_num % 8))) != 0 {
sector[0..12].copy_from_slice(&CD_SYNC_HEADER);
sector.generate_ecc();
}
}
Ok(frame_res + sub_res)
}
}