cbm/disk/
d71.rs

1use std::cell::RefCell;
2use std::io;
3use std::path::Path;
4use std::rc::Rc;
5
6use disk::bam::{BAMFormat, BAMSection};
7use disk::block::{BlockDevice, BlockDeviceRef, ImageBlockDevice};
8use disk::error::DiskError;
9use disk::header::{Header, HeaderFormat};
10use disk::{self, BAMRef, Disk, DiskFormat, Geometry, Id, Image, Location, Track, BAM};
11
12const TRACK_COUNT: usize = 70;
13const HEADER_DOUBLE_SIDED_FLAG_OFFSET: usize = 0x03;
14const HEADER_DOUBLE_SIDED_VALUE: u8 = 0x80;
15
16/// A description of the header format for this disk image type.
17static HEADER_FORMAT: HeaderFormat = HeaderFormat {
18    location: Location(18, 0),
19    first_directory_offset: 0x00,
20    disk_dos_version_offset: 0x02,
21    disk_name_offset: 0x90,
22    disk_id_offset: 0xA2,
23    dos_type_offset: 0xA5,
24    padding_offsets: &[0xA0, 0xA1, 0xA4, 0xA7, 0xA8, 0xA9, 0xAA],
25    expected_dos_version: 0x41,
26    expected_dos_type: Id([b'2', b'A']),
27    double_sided_flag_expectation: Some((
28        HEADER_DOUBLE_SIDED_FLAG_OFFSET,
29        HEADER_DOUBLE_SIDED_VALUE,
30    )),
31};
32
33/// A description of the BAM format for this disk image type.
34static BAM_FORMAT: BAMFormat = BAMFormat {
35    sections: &[
36        BAMSection {
37            bitmap_location: Location(18, 0),
38            bitmap_offset: 0x05,
39            bitmap_size: 3,
40            bitmap_stride: 4,
41            free_location: Location(18, 0),
42            free_offset: 0x04,
43            free_stride: 4,
44            tracks: 35,
45        },
46        BAMSection {
47            bitmap_location: Location(53, 0),
48            bitmap_offset: 0x00,
49            bitmap_size: 3,
50            bitmap_stride: 3,
51            free_location: Location(18, 0),
52            free_offset: 0xDD,
53            free_stride: 1,
54            tracks: 35,
55        },
56    ],
57};
58
59/// A description of the disk format for this disk image type.
60static DISK_FORMAT: DiskFormat = DiskFormat {
61    directory_track: 18,
62    first_directory_sector: 1,
63    reserved_track: 53,
64    first_track: 1,
65    last_track: 70,
66    last_nonstandard_track: 70,
67    interleave: 6,
68    directory_interleave: 3,
69    geos: false,
70    tracks: &TRACKS,
71    header: &HEADER_FORMAT,
72    bam: &BAM_FORMAT,
73};
74
75pub static GEOMETRY: Geometry = Geometry {
76    track_layouts: &TRACKS,
77    tracks: TRACK_COUNT as u8,
78    with_error_table: false,
79};
80
81pub static GEOMETRY_ERRORS: Geometry = Geometry {
82    track_layouts: &TRACKS,
83    tracks: TRACK_COUNT as u8,
84    with_error_table: true,
85};
86
87static ALLOWED_GEOMETRIES: [&Geometry; 2] = [&GEOMETRY, &GEOMETRY_ERRORS];
88
89#[cfg_attr(rustfmt, rustfmt_skip)]
90static TRACKS: [Track; TRACK_COUNT+1] = [
91    Track { sectors: 0,  sector_offset:    0, byte_offset: 0, }, // There is no sector 0
92    Track { sectors: 21, sector_offset:    0, byte_offset: 0x00000, }, // 1
93    Track { sectors: 21, sector_offset:   21, byte_offset: 0x01500, }, // 2
94    Track { sectors: 21, sector_offset:   42, byte_offset: 0x02A00, }, // 3
95    Track { sectors: 21, sector_offset:   63, byte_offset: 0x03F00, }, // 4
96    Track { sectors: 21, sector_offset:   84, byte_offset: 0x05400, }, // 5
97    Track { sectors: 21, sector_offset:  105, byte_offset: 0x06900, }, // 6
98    Track { sectors: 21, sector_offset:  126, byte_offset: 0x07E00, }, // 7
99    Track { sectors: 21, sector_offset:  147, byte_offset: 0x09300, }, // 8
100    Track { sectors: 21, sector_offset:  168, byte_offset: 0x0A800, }, // 9
101    Track { sectors: 21, sector_offset:  189, byte_offset: 0x0BD00, }, // 10
102    Track { sectors: 21, sector_offset:  210, byte_offset: 0x0D200, }, // 11
103    Track { sectors: 21, sector_offset:  231, byte_offset: 0x0E700, }, // 12
104    Track { sectors: 21, sector_offset:  252, byte_offset: 0x0FC00, }, // 13
105    Track { sectors: 21, sector_offset:  273, byte_offset: 0x11100, }, // 14
106    Track { sectors: 21, sector_offset:  294, byte_offset: 0x12600, }, // 15
107    Track { sectors: 21, sector_offset:  315, byte_offset: 0x13B00, }, // 16
108    Track { sectors: 21, sector_offset:  336, byte_offset: 0x15000, }, // 17
109    Track { sectors: 19, sector_offset:  357, byte_offset: 0x16500, }, // 18
110    Track { sectors: 19, sector_offset:  376, byte_offset: 0x17800, }, // 19
111    Track { sectors: 19, sector_offset:  395, byte_offset: 0x18B00, }, // 20
112    Track { sectors: 19, sector_offset:  414, byte_offset: 0x19E00, }, // 21
113    Track { sectors: 19, sector_offset:  433, byte_offset: 0x1B100, }, // 22
114    Track { sectors: 19, sector_offset:  452, byte_offset: 0x1C400, }, // 23
115    Track { sectors: 19, sector_offset:  471, byte_offset: 0x1D700, }, // 24
116    Track { sectors: 18, sector_offset:  490, byte_offset: 0x1EA00, }, // 25
117    Track { sectors: 18, sector_offset:  508, byte_offset: 0x1FC00, }, // 26
118    Track { sectors: 18, sector_offset:  526, byte_offset: 0x20E00, }, // 27
119    Track { sectors: 18, sector_offset:  544, byte_offset: 0x22000, }, // 28
120    Track { sectors: 18, sector_offset:  562, byte_offset: 0x23200, }, // 29
121    Track { sectors: 18, sector_offset:  580, byte_offset: 0x24400, }, // 30
122    Track { sectors: 17, sector_offset:  598, byte_offset: 0x25600, }, // 31
123    Track { sectors: 17, sector_offset:  615, byte_offset: 0x26700, }, // 32
124    Track { sectors: 17, sector_offset:  632, byte_offset: 0x27800, }, // 33
125    Track { sectors: 17, sector_offset:  649, byte_offset: 0x28900, }, // 34
126    Track { sectors: 17, sector_offset:  666, byte_offset: 0x29A00, }, // 35
127    Track { sectors: 21, sector_offset:  683, byte_offset: 0x2AB00, }, // 36
128    Track { sectors: 21, sector_offset:  704, byte_offset: 0x2C000, }, // 37
129    Track { sectors: 21, sector_offset:  725, byte_offset: 0x2D500, }, // 38
130    Track { sectors: 21, sector_offset:  746, byte_offset: 0x2EA00, }, // 39
131    Track { sectors: 21, sector_offset:  767, byte_offset: 0x2FF00, }, // 40
132    Track { sectors: 21, sector_offset:  788, byte_offset: 0x31400, }, // 41
133    Track { sectors: 21, sector_offset:  809, byte_offset: 0x32900, }, // 42
134    Track { sectors: 21, sector_offset:  830, byte_offset: 0x33E00, }, // 43
135    Track { sectors: 21, sector_offset:  851, byte_offset: 0x35300, }, // 44
136    Track { sectors: 21, sector_offset:  872, byte_offset: 0x36800, }, // 45
137    Track { sectors: 21, sector_offset:  893, byte_offset: 0x37D00, }, // 46
138    Track { sectors: 21, sector_offset:  914, byte_offset: 0x39200, }, // 47
139    Track { sectors: 21, sector_offset:  935, byte_offset: 0x3A700, }, // 48
140    Track { sectors: 21, sector_offset:  956, byte_offset: 0x3BC00, }, // 49
141    Track { sectors: 21, sector_offset:  977, byte_offset: 0x3D100, }, // 50
142    Track { sectors: 21, sector_offset:  998, byte_offset: 0x3E600, }, // 51
143    Track { sectors: 21, sector_offset: 1019, byte_offset: 0x3FB00, }, // 52
144    Track { sectors: 19, sector_offset: 1040, byte_offset: 0x41000, }, // 53
145    Track { sectors: 19, sector_offset: 1059, byte_offset: 0x42300, }, // 54
146    Track { sectors: 19, sector_offset: 1078, byte_offset: 0x43600, }, // 55
147    Track { sectors: 19, sector_offset: 1097, byte_offset: 0x44900, }, // 56
148    Track { sectors: 19, sector_offset: 1116, byte_offset: 0x45C00, }, // 57
149    Track { sectors: 19, sector_offset: 1135, byte_offset: 0x46F00, }, // 58
150    Track { sectors: 19, sector_offset: 1154, byte_offset: 0x48200, }, // 59
151    Track { sectors: 18, sector_offset: 1173, byte_offset: 0x49500, }, // 60
152    Track { sectors: 18, sector_offset: 1191, byte_offset: 0x4A700, }, // 61
153    Track { sectors: 18, sector_offset: 1209, byte_offset: 0x4B900, }, // 62
154    Track { sectors: 18, sector_offset: 1227, byte_offset: 0x4CB00, }, // 63
155    Track { sectors: 18, sector_offset: 1245, byte_offset: 0x4DD00, }, // 64
156    Track { sectors: 18, sector_offset: 1263, byte_offset: 0x4EF00, }, // 65
157    Track { sectors: 17, sector_offset: 1281, byte_offset: 0x50100, }, // 66
158    Track { sectors: 17, sector_offset: 1298, byte_offset: 0x51200, }, // 67
159    Track { sectors: 17, sector_offset: 1315, byte_offset: 0x52300, }, // 68
160    Track { sectors: 17, sector_offset: 1332, byte_offset: 0x53400, }, // 69
161    Track { sectors: 17, sector_offset: 1349, byte_offset: 0x54500, }, // 70
162];
163
164/// Represent a 1571 disk image in D71 format.  Geometries for this disk image
165/// type can describe images with and without attached error tables.
166pub struct D71 {
167    blocks: Rc<RefCell<ImageBlockDevice>>,
168    header: Option<Header>,
169    bam: Option<BAMRef>,
170    format: Option<DiskFormat>,
171}
172
173impl D71 {
174    fn new(image: Image) -> io::Result<D71> {
175        // Determine the disk image geometry
176        let geometry = match Geometry::find_by_size(image.len(), &ALLOWED_GEOMETRIES[..]) {
177            Some(geometry) => geometry,
178            None => return Err(DiskError::InvalidLayout.into()),
179        };
180
181        let blocks = ImageBlockDevice::new(image, geometry);
182
183        let mut d71 = D71 {
184            blocks: Rc::new(RefCell::new(blocks)),
185            format: None,
186            header: None,
187            bam: None,
188        };
189        d71.initialize();
190        Ok(d71)
191    }
192
193    /// Open an existing D71 disk image as read-only (if `writable` is false)
194    /// or read-write (if `writable` is true).
195    pub fn open<P: AsRef<Path>>(path: P, writable: bool) -> io::Result<D71> {
196        let image = if writable {
197            Image::open_read_write(path)?
198        } else {
199            Image::open_read_only(path)?
200        };
201        Self::new(image)
202    }
203
204    /// Create a new D71 disk image.  If `create_new` is true, no file is
205    /// allowed to exist at the target location.  If false, any existing
206    /// file will be overwritten.
207    pub fn create<P: AsRef<Path>>(
208        path: P,
209        geometry: &Geometry,
210        create_new: bool,
211    ) -> io::Result<D71> {
212        Self::new(Image::create(path, geometry.size(), create_new)?)
213    }
214
215    /// Create a new in-memory D71 disk image.
216    pub fn open_memory(geometry: &Geometry) -> io::Result<D71> {
217        Self::new(Image::open_memory(geometry.size())?)
218    }
219
220    /// Return the geometry for D71 disks.
221    #[inline]
222    pub fn geometry(with_error_table: bool) -> &'static Geometry {
223        if with_error_table {
224            &GEOMETRY_ERRORS
225        } else {
226            &GEOMETRY
227        }
228    }
229}
230
231impl Disk for D71 {
232    #[inline]
233    fn native_disk_format(&self) -> &'static DiskFormat {
234        &DISK_FORMAT
235    }
236
237    fn disk_format(&self) -> io::Result<&DiskFormat> {
238        match self.format {
239            Some(ref format) => Ok(format),
240            None => Err(DiskError::Unformatted.into()),
241        }
242    }
243
244    fn disk_format_mut(&mut self) -> io::Result<&mut DiskFormat> {
245        match self.format {
246            Some(ref mut format) => Ok(format),
247            None => Err(DiskError::Unformatted.into()),
248        }
249    }
250
251    fn set_disk_format(&mut self, disk_format: Option<DiskFormat>) {
252        self.format = disk_format;
253    }
254
255    fn blocks(&self) -> BlockDeviceRef {
256        self.blocks.clone()
257    }
258
259    fn blocks_ref(&self) -> ::std::cell::Ref<'_, BlockDevice> {
260        self.blocks.borrow()
261    }
262
263    fn blocks_ref_mut(&self) -> ::std::cell::RefMut<'_, BlockDevice> {
264        self.blocks.borrow_mut()
265    }
266
267    fn header<'a>(&'a self) -> io::Result<&'a Header> {
268        match &self.header {
269            &Some(ref header) => Ok(&header),
270            &None => Err(DiskError::Unformatted.into()),
271        }
272    }
273
274    fn header_mut<'a>(&'a mut self) -> io::Result<&'a mut Header> {
275        self.blocks.borrow().check_writability()?;
276        match &mut self.header {
277            &mut Some(ref mut header) => Ok(header),
278            &mut None => Err(DiskError::Unformatted.into()),
279        }
280    }
281
282    fn set_header(&mut self, header: Option<Header>) {
283        self.header = header;
284    }
285
286    fn flush_header(&mut self) -> io::Result<()> {
287        let header = match self.header {
288            Some(ref mut header) => header,
289            None => return Err(DiskError::Unformatted.into()),
290        };
291        header.write(self.blocks.clone(), &HEADER_FORMAT)
292    }
293
294    fn bam(&self) -> io::Result<BAMRef> {
295        match &self.bam {
296            &Some(ref bam) => Ok(bam.clone()),
297            &None => Err(DiskError::Unformatted.into()),
298        }
299    }
300
301    fn set_bam(&mut self, bam: Option<BAM>) {
302        disk::set_bam(&mut self.bam, bam);
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    #[allow(unused_imports)]
309    use super::*;
310    use disk::BLOCK_SIZE;
311
312    #[test]
313    fn test_track_consistency() {
314        let mut sector_offset = 0;
315        let mut byte_offset = 0;
316        for track in super::TRACKS.iter() {
317            assert_eq!(track.sector_offset, sector_offset);
318            assert_eq!(track.byte_offset, byte_offset);
319            sector_offset += track.sectors as u16;
320            byte_offset += track.sectors as u32 * BLOCK_SIZE as u32;
321        }
322    }
323}