chd/compression/lzma.rs
1use crate::compression::{
2 CodecImplementation, CompressionCodec, CompressionCodecType, DecompressResult,
3};
4use crate::error::{Error, Result};
5use crate::header::CodecType;
6use lzma_rs::decompress::raw::{LzmaDecoder, LzmaParams, LzmaProperties};
7use std::io::Cursor;
8/// LZMA (lzma) decompression codec.
9///
10/// ## Format Details
11/// CHD compresses LZMA hunks without an xz stream header using
12/// the default compression settings for LZMA 19.0. These settings are
13///
14/// * Literal Context Bits (`lc`): 3
15/// * Literal Position Bits (`lp`): 0
16/// * Position Bits (`pb`): 2
17///
18/// The dictionary size is determined via the following algorithm with a level of 9, and a
19/// reduction size of hunk size.
20///
21/// ```rust
22/// fn get_lzma_dict_size(level: u32, reduce_size: u32) -> u32 {
23/// let mut dict_size = if level <= 5 {
24/// 1 << (level * 2 + 14)
25/// } else if level <= 7 {
26/// 1 << 25
27/// } else {
28/// 1 << 26
29/// };
30///
31/// // This does the same thing as LzmaEnc.c when determining dict_size
32/// if dict_size > reduce_size {
33/// for i in 11..=30 {
34/// if reduce_size <= (2u32 << i) {
35/// dict_size = 2u32 << i;
36/// break;
37/// }
38/// if reduce_size <= (3u32 << i) {
39/// dict_size = 3u32 << i;
40/// break;
41/// }
42/// }
43/// }
44/// dict_size
45/// }
46/// ```
47///
48/// ## Buffer Restrictions
49/// Each compressed LZMA hunk decompresses to a hunk-sized chunk.
50/// The input buffer must contain exactly enough data to fill the output buffer
51/// when decompressed.
52pub struct LzmaCodec {
53 // The LZMA codec for CHD uses raw LZMA chunks without a stream header. The result
54 // is that the chunks are encoded with the defaults used in LZMA 19.0.
55 // These defaults are lc = 3, lp = 0, pb = 2.
56 engine: LzmaDecoder,
57}
58
59impl CompressionCodec for LzmaCodec {}
60
61/// MAME/libchdr uses an ancient LZMA 19.00.
62///
63/// To match the proper dictionary size, we copy the algorithm from
64/// [`LzmaEnc::LzmaEncProps_Normalize`](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/deps/lzma-19.00/src/LzmaEnc.c#L52).
65fn get_lzma_dict_size(level: u32, reduce_size: u32) -> u32 {
66 let mut dict_size = if level <= 5 {
67 1 << (level * 2 + 14)
68 } else if level <= 7 {
69 1 << 25
70 } else {
71 1 << 26
72 };
73
74 // this does the same thing as LzmaEnc.c when determining dict_size
75 if dict_size > reduce_size {
76 // might be worth converting this to a while loop for const
77 // depends if we can const-propagate hunk_size.
78 // todo: #[feature(const_inherent_unchecked_arith)
79 for i in 11..=30 {
80 if reduce_size <= (2u32 << i) {
81 dict_size = 2u32 << i;
82 break;
83 }
84 if reduce_size <= (3u32 << i) {
85 dict_size = 3u32 << i;
86 break;
87 }
88 }
89 }
90
91 dict_size
92}
93
94impl CompressionCodecType for LzmaCodec {
95 fn codec_type(&self) -> CodecType
96 where
97 Self: Sized,
98 {
99 CodecType::LzmaV5
100 }
101}
102
103#[cfg(not(feature = "fast_lzma"))]
104impl CodecImplementation for LzmaCodec {
105 fn new(hunk_size: u32) -> Result<Self> {
106 Ok(LzmaCodec {
107 engine: LzmaDecoder::new(
108 LzmaParams::new(
109 LzmaProperties {
110 lc: 3,
111 lp: 0,
112 pb: 2,
113 },
114 get_lzma_dict_size(9, hunk_size),
115 None,
116 ),
117 None,
118 )
119 .map_err(|_| Error::DecompressionError)?,
120 })
121 }
122
123 fn decompress(&mut self, input: &[u8], mut output: &mut [u8]) -> Result<DecompressResult> {
124 let mut read = Cursor::new(input);
125 let len = output.len();
126 self.engine.reset(Some(Some(len as u64)));
127 self.engine
128 .decompress(&mut read, &mut output)
129 .map_err(|_| Error::DecompressionError)?;
130 Ok(DecompressResult::new(len, read.position() as usize))
131 }
132}
133
134#[cfg(feature = "fast_lzma")]
135impl CodecImplementation for LzmaCodec {
136 fn new(hunk_size: u32) -> Result<Self> {
137 let dict_size = get_lzma_dict_size(9, hunk_size);
138 Ok(LzmaCodec {
139 engine: LzmaDecoder::new_with_buffer(
140 LzmaParams::new(
141 LzmaProperties {
142 lc: 3,
143 lp: 0,
144 pb: 2,
145 },
146 dict_size,
147 None,
148 ),
149 None,
150 vec![0; dict_size as usize],
151 )
152 .map_err(|_| Error::CodecError)?,
153 })
154 }
155
156 fn decompress(&mut self, input: &[u8], mut output: &mut [u8]) -> Result<DecompressResult> {
157 use lzma_rs::decompress::raw::LzAccumBuffer;
158 let mut read = Cursor::new(input);
159 let len = output.len();
160 self.engine.reset(Some(Some(len as u64)));
161 self.engine
162 .decompress_with_buffer::<LzAccumBuffer<_>, _, _>(&mut read, &mut output)
163 .map_err(|_| Error::DecompressionError)?;
164 Ok(DecompressResult::new(len, read.position() as usize))
165 }
166}