1use displaydoc::Display;
21use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
22use thiserror::Error;
23use tracing::debug;
24use winnow::{
25 Parser,
26 binary::{
27 be_u16, be_u32,
28 bits::{bits, take as bits_take},
29 u8,
30 },
31 error::{ContextError, StrContext},
32 token::take,
33};
34
35use crate::{
36 Mmc,
37 mmc::{Cdb, LEADOUT_TRACK, MmcCommand, MmcDirection, MmcError},
38};
39
40impl Mmc {
42 pub fn cd_text(&self) -> Result<Vec<u8>, MmcTocError> {
44 let mut data = self.read_toc(ResponseFormat::Cdtext)?;
45 let input = &mut data.as_slice();
46 parse_header(input)?;
47
48 let cdtext_len = input.len();
49 data.drain(0..HEADER_SIZE);
52 data.truncate(cdtext_len);
53
54 return Ok(data);
55
56 const HEADER_SIZE: usize = 4;
57 }
58
59 pub fn cd_type(&self) -> Result<CdType, MmcTocError> {
61 let data = self.read_toc(ResponseFormat::FullToc {
62 session_number: u8::default(),
63 })?;
64 let input = &mut data.as_slice();
65 parse_header(input)?;
66
67 let (
68 _session_num,
69 (_adr, _control),
70 _tno,
71 _point,
72 _min,
73 _sec,
74 _frame,
75 _zero,
76 _pmin,
77 psec,
78 _pframe,
79 ) = (
80 u8, bits((
82 bits_take::<_, u8, _, ContextError>(ADR_BITS),
83 bits_take::<_, u8, _, _>(CONTROL_BITS),
84 )),
85 u8, u8.verify(|point| *point == POINT),
87 u8, u8, u8, u8.verify(|zero| *zero == 0),
91 u8, u8, u8, )
95 .context(StrContext::Label("raw toc descriptor"))
96 .parse_next(input)?;
97
98 return CdType::try_from(psec).map_err(MmcTocError::from);
99
100 const ADR_BITS: usize = 4;
101 const CONTROL_BITS: usize = 4;
102 const POINT: u8 = 0xA0; }
104
105 pub fn last_sector(&self) -> Result<u32, MmcTocError> {
107 let data = self.read_toc(ResponseFormat::Toc {
108 address_format: AddressFormat::Lba,
109 track_number: LEADOUT_TRACK,
110 })?;
111 let input = &mut data.as_slice();
112 parse_header(input)?;
113
114 let (_, (_adr, _control), _track_num, _, track_start_address) = (
115 u8, bits((
117 bits_take::<_, u8, _, ContextError>(ADR_BITS), bits_take::<_, u8, _, _>(CONTROL_BITS), )),
120 u8.verify(|track_num| *track_num == LEADOUT_TRACK),
121 u8, be_u32, )
124 .context(StrContext::Label("formatted toc descriptor"))
125 .parse_next(input)?;
126
127 return Ok(track_start_address.saturating_sub(1));
131
132 const ADR_BITS: usize = 4;
133 const CONTROL_BITS: usize = 4;
134 }
135
136 fn read_toc(&self, format: ResponseFormat) -> Result<Vec<u8>, MmcTocError> {
137 let mut buf = vec![0; DEFAULT_BUFFER_SIZE];
138 let mut cdb = Cdb::default();
139
140 cdb[0] = MmcCommand::ReadToc as u8;
141 if let ResponseFormat::Toc { address_format, .. }
142 | ResponseFormat::SessionInfo { address_format } = format
143 && let AddressFormat::Time = address_format
144 {
145 cdb[1] |= 1 << TIME_BITPOS;
146 }
147 cdb[2] = format.discriminant();
148 if let ResponseFormat::Toc {
149 track_number: num, ..
150 }
151 | ResponseFormat::FullToc {
152 session_number: num,
153 } = format
154 {
155 cdb[6] = num;
156 }
157 cdb[7..9].copy_from_slice(&(buf.len() as u16).to_be_bytes());
158
159 self.run_command(Some(MmcDirection::Read), buf.as_mut_slice(), cdb)?;
160
161 let descriptor_length = buf[0..DTORLEN_SIZE]
162 .try_into()
163 .map(u16::from_be_bytes)
164 .expect("buffer length is greater than two bytes");
165 let data_size = usize::from(descriptor_length) + HEADER_SIZE; if data_size > buf.len() {
167 buf.resize(data_size, u8::default());
168 cdb[7..9].copy_from_slice(&(buf.len() as u16).to_be_bytes());
169 self.run_command(Some(MmcDirection::Read), buf.as_mut_slice(), cdb)?;
170 }
171
172 return Ok(buf);
173
174 const DEFAULT_BUFFER_SIZE: usize = 64;
175 const TIME_BITPOS: usize = 1;
176 const DTORLEN_SIZE: usize = 2;
177 const HEADER_SIZE: usize = 4;
178 }
179}
180
181fn parse_header(input: &mut &[u8]) -> Result<(u8, u8), MmcTocError> {
183 debug!(?input, len = input.len(), "parse_header()");
184
185 let descriptors_length = be_u16::<_, ContextError>
186 .context(StrContext::Label("READ TOC/PMA/ATIP header"))
187 .parse_next(input)?;
188 let (first_byte, second_byte, descriptors) =
189 (u8::<_, ContextError>, u8, take(descriptors_length))
190 .context(StrContext::Label("READ TOC/PMA/ATIP header"))
191 .parse_next(input)?;
192 *input = descriptors;
193
194 Ok((first_byte, second_byte))
195}
196
197#[non_exhaustive]
199#[derive(Debug, Display, Error)]
200pub enum MmcTocError {
201 Os(#[from] MmcError),
203
204 InvalidResponse(String),
206}
207impl From<ContextError> for MmcTocError {
208 fn from(err: ContextError) -> Self {
209 Self::InvalidResponse(err.to_string())
210 }
211}
212impl<T: TryFromPrimitive> From<TryFromPrimitiveError<T>> for MmcTocError {
213 fn from(err: TryFromPrimitiveError<T>) -> Self {
214 Self::InvalidResponse(err.to_string())
215 }
216}
217
218#[allow(unused)]
219#[derive(Clone, Copy, Debug)]
220enum AddressFormat {
221 Lba,
222 Time,
223}
224
225#[repr(u8)]
226#[allow(unused)]
227#[derive(Clone, Copy, Debug)]
228enum ResponseFormat {
229 Toc {
230 address_format: AddressFormat,
231 track_number: u8,
232 } = 0b0000,
233 SessionInfo {
234 address_format: AddressFormat,
235 } = 0b0001,
236 FullToc {
237 session_number: u8,
238 } = 0b0010,
239 Pma = 0b0011,
240 Atip = 0b0100,
241 Cdtext = 0b0101,
242}
243impl ResponseFormat {
244 fn discriminant(&self) -> u8 {
245 unsafe { *(&raw const *self).cast::<u8>() }
251 }
252}
253
254#[repr(u8)]
256#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, TryFromPrimitive)]
257pub enum CdType {
258 #[default]
260 CddaOrCdData = 0x00,
261
262 Cdi = 0x10,
264
265 CdromXa = 0x20,
267}
268
269#[cfg(test)]
270mod tests {
271 use tracing::info;
272
273 use super::*;
274
275 #[test_log::test(test)]
276 #[ignore = "requires a disc drive with mmc"]
277 fn cd_text() {
278 let cd_text = Mmc::new().unwrap().cd_text().unwrap();
279 info!(?cd_text, len = cd_text.len());
280 assert!(!cd_text.is_empty());
281 }
282
283 #[test_log::test(test)]
284 #[ignore = "requires a disc drive with mmc"]
285 fn cd_type() {
286 let cd_type = Mmc::new().unwrap().cd_type().unwrap();
287 info!(?cd_type);
288 }
289
290 #[test_log::test(test)]
291 #[ignore = "requires a disc drive with mmc"]
292 fn last_sector() {
293 let last_sector = Mmc::new().unwrap().last_sector().unwrap();
294 info!(?last_sector);
295 }
296}