Skip to main content

image_rider/disk_format/stx/
disk.rs

1use log::{debug, error, info};
2
3use std::fs::File;
4use std::io::Write;
5use std::path::PathBuf;
6
7use nom::bytes::complete::{tag, take};
8use nom::number::complete::{le_u16, le_u8};
9use nom::IResult;
10
11use std::fmt::{Display, Formatter, Result};
12
13use crate::config::Config;
14use crate::disk_format::image::DiskImageSaver;
15use crate::disk_format::stx::track::{stx_tracks_parser, STXTrack};
16use crate::disk_format::stx::SanityCheck;
17
18/// A STX disk image
19#[derive(Debug)]
20pub struct STXDisk<'a> {
21    /// The disk header
22    pub stx_disk_header: STXDiskHeader<'a>,
23
24    /// The disk tracks
25    pub stx_tracks: Vec<STXTrack<'a>>,
26}
27
28/// Format a STXDisk for display
29impl Display for STXDisk<'_> {
30    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
31        write!(f, "header: {}", self.stx_disk_header)
32    }
33}
34
35// impl DiskImageParser for STXDisk<'_> {
36//     fn parse_disk_image<'a>(
37//         &self,
38//         _config: &Config,
39//         _filename: &str,
40//         data: &'a [u8],
41//     ) -> IResult<&'a [u8], DiskImage<'a>> {
42//         let (i, parse_result) = stx_disk_parser(data)?;
43//         Ok((i, DiskImage::STX(parse_result)))
44//     }
45// }
46
47/// Heuristic guesses for what kind of disk this is
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub struct STXDiskGuess<'a> {
50    /// The raw image data
51    pub data: &'a [u8],
52}
53
54impl DiskImageSaver for STXDisk<'_> {
55    /// This saves the underlying image on this disk.
56    /// This can be a FAT disk image, an ST disk, or a custom disk image
57    /// that may or may not be copy-protected.
58    fn save_disk_image(
59        &self,
60        _config: &Config,
61        _selected_filename: Option<&str>,
62        filename: &str,
63    ) -> std::result::Result<(), crate::error::Error> {
64        // It may be more efficient to return sector-size &[u8] iterators
65        let disk_image_data: Vec<u8> = self
66            .stx_tracks
67            .iter()
68            .filter(|s| s.sector_data.is_some())
69            .flat_map(|s| s.sector_data.as_ref().unwrap().iter())
70            .flat_map(|bytes| (*bytes).iter())
71            .copied()
72            .collect();
73        info!("Found image data, writing data");
74        let filename = PathBuf::from(filename);
75        let file_result = File::create(filename);
76        match file_result {
77            Ok(mut file) => {
78                let _res = file.write_all(&disk_image_data);
79            }
80            Err(e) => error!("Error opening file: {}", e),
81        }
82        Ok(())
83    }
84}
85
86/// STXDiskHeader contains information about an Atari ST STX floppy disk image header
87/// 16 bytes
88#[derive(Debug)]
89pub struct STXDiskHeader<'a> {
90    /// The disk identifier, "RSY\0"
91    pub disk_id: &'a [u8],
92    /// The version of the disk image
93    /// Usually version 3
94    pub version: u16,
95    /// A 16-bit integer that indicates the tool used to create the image
96    pub tool_used: u16,
97    /// First reserved area, two reserved bytes
98    pub reserved_area_1: &'a [u8],
99    /// The number of tracks on the disk
100    pub track_count: u8,
101    /// Whether the disk is in the new format
102    pub new_format: u8,
103    /// Second reserved area, four reserved bytes
104    pub reserved_area_2: &'a [u8],
105}
106
107/// Perform sanity checks for a disk header
108/// For now, these are done post-parsing of the section
109/// These are generally less strict than things like magic number identification
110/// but are good indicators the data may be corrupted
111impl SanityCheck for STXDiskHeader<'_> {
112    fn check(&self) -> bool {
113        if self.track_count > 164 {
114            debug!("Disk track count is greater than 164: {}", self.track_count);
115            false
116        } else {
117            true
118        }
119    }
120}
121
122/// Format a STXDiskHeader for display
123impl Display for STXDiskHeader<'_> {
124    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
125        write!(
126            f,
127            "version: {}, tool_used: {}, reserved_area_1: {:?}, track_count: {}, ",
128            self.version, self.tool_used, self.reserved_area_1, self.track_count
129        )?;
130        write!(
131            f,
132            "new_format: {}, reserved_area_2: {:?}",
133            self.new_format, self.reserved_area_2
134        )
135    }
136}
137
138/// The track or sector image data can be located in several places, depending on the
139/// fuzzy masks and track flags
140pub fn stx_disk_parser(i: &[u8]) -> IResult<&[u8], STXDisk<'_>> {
141    let (i, stx_disk_header) = stx_disk_header_parser(i)?;
142
143    if !stx_disk_header.check() {
144        error!("Invalid data");
145        panic!("Invalid data");
146    }
147
148    info!("Disk header: {}", stx_disk_header);
149
150    let (i, tracks) = stx_tracks_parser(stx_disk_header.track_count as usize)(i)?;
151
152    let stx_disk = STXDisk {
153        stx_disk_header,
154        stx_tracks: tracks,
155    };
156
157    Ok((i, stx_disk))
158}
159
160// TODO: Verify that this is reading correctly
161/// Parse STX disks
162pub fn stx_disk_header_parser(i: &[u8]) -> IResult<&[u8], STXDiskHeader<'_>> {
163    // will consume bytes if the input begins with "RSY" + 0
164    // magic number
165    let (i, disk_id) = tag("RSY\0")(i)?;
166
167    // version
168    let (i, version) = le_u16(i)?;
169
170    // tool used
171    let (i, tool_used) = le_u16(i)?;
172
173    // 2 reserved bytes
174    let (i, reserved_area_1) = take(2_usize)(i)?;
175
176    let (i, track_count) = le_u8(i)?;
177    let (i, new_format) = le_u8(i)?;
178
179    // 4 reserved bytes
180    let (i, reserved_area_2) = take(4_usize)(i)?;
181
182    let stx_disk_header = STXDiskHeader {
183        disk_id,
184        version,
185        tool_used,
186        reserved_area_1,
187        track_count,
188        new_format,
189        reserved_area_2,
190    };
191
192    Ok((i, stx_disk_header))
193}
194
195#[cfg(test)]
196mod tests {
197    use super::stx_disk_header_parser;
198
199    /// Test parsing a STX disk header
200    #[test]
201    fn stx_disk_valid_header_parser_works() {
202        // Standard "public tool" / stock Atari ST image
203        // version 3, tool 1, 82 tracks, new format 2
204        let stx_disk_header: [u8; 16] = [
205            0x52, 0x53, 0x59, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x52, 0x02, 0x00, 0x00,
206            0x00, 0x00,
207        ];
208
209        let stx_disk_header_parser_result = stx_disk_header_parser(&stx_disk_header);
210
211        match stx_disk_header_parser_result {
212            Ok((_, res)) => {
213                assert_eq!(res.disk_id, [0x52, 0x53, 0x59, 0x00]);
214                assert_eq!(res.version, 0x03);
215                assert_eq!(res.tool_used, 0x01);
216                assert_eq!(res.reserved_area_1, [0x00, 0x00]);
217                assert_eq!(res.track_count, 0x52);
218                assert_eq!(res.new_format, 0x02);
219                assert_eq!(res.reserved_area_2, [0x00, 0x00, 0x00, 0x00]);
220            }
221            Err(e) => panic!("Parsing failed on the STX disk header: {}", e),
222        }
223    }
224
225    /// Test parsing an invalid STX disk header
226    #[test]
227    #[should_panic(
228        expected = "Parsing failed on the STX disk header: Parsing Error: Error { input: [82, 83, 96, 0, 3, 0, 1, 0, 0, 0, 82, 2, 0, 0, 0, 0], code: Tag }"
229    )]
230    fn stx_disk_invalid_header_parser_fails() {
231        // Standard "public tool" / stock Atari ST image
232        // version 3, tool 1, 82 tracks, new format 2
233        // invalid magic number
234        let stx_disk_header: [u8; 16] = [
235            0x52, 0x53, 0x60, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x52, 0x02, 0x00, 0x00,
236            0x00, 0x00,
237        ];
238
239        let stx_disk_header_parser_result = stx_disk_header_parser(&stx_disk_header);
240
241        match stx_disk_header_parser_result {
242            Ok((_, _)) => panic!("Should fail parsing"),
243            Err(e) => panic!("Parsing failed on the STX disk header: {}", e),
244        }
245    }
246}