cbm/disk/
header.rs

1use std::fmt;
2use std::io;
3
4use disk::block::{BlockDeviceRef, Location};
5pub use disk::error::DiskError;
6use disk::format::DiskFormat;
7use disk::geos::GEOSDiskHeader;
8use disk::{self, Id, PADDING_BYTE};
9use petscii::Petscii;
10
11/// A HeaderFormat describes how header information is stored for a particular
12/// disk image format.
13pub struct HeaderFormat {
14    pub location: Location,
15    // offsets
16    pub first_directory_offset: usize,
17    pub disk_dos_version_offset: usize,
18    pub disk_name_offset: usize,
19    pub disk_id_offset: usize,
20    pub dos_type_offset: usize,
21    pub padding_offsets: &'static [u8],
22    // expectations
23    pub expected_dos_version: u8,
24    pub expected_dos_type: Id,
25    pub double_sided_flag_expectation: Option<(usize, u8)>,
26}
27
28pub struct Header {
29    // http://unusedino.de/ec64/technical/formats/d64.html
30    // says to not trust this field.
31    pub first_directory_sector: Location,
32    pub disk_dos_version_type: u8,
33    pub disk_name: Petscii,
34    pub disk_id: Id,
35    pub dos_type: Id,
36    pub geos: Option<GEOSDiskHeader>,
37}
38
39impl Header {
40    pub fn new(
41        header_format: &HeaderFormat,
42        disk_format: &DiskFormat,
43        name: &Petscii,
44        id: &Id,
45    ) -> Header {
46        Header {
47            first_directory_sector: disk_format.first_directory_location(),
48            disk_dos_version_type: header_format.expected_dos_version,
49            disk_name: name.clone(),
50            disk_id: id.clone(),
51            dos_type: header_format.expected_dos_type,
52            geos: None,
53        }
54    }
55
56    /// Read a header from disk using the provided header format.
57    pub fn read(blocks: BlockDeviceRef, format: &HeaderFormat) -> io::Result<Header> {
58        let blocks = blocks.borrow();
59        let block = blocks.sector(format.location)?;
60
61        // We only handle disks with the standard CBM DOS type for its format (e.g.
62        // "2A" for 1541).  Some alternate disk formats (ProfDOS, PrologicDOS
63        // 40-track, ProSpeed 40-track) will use a different type, but we don't
64        // currently support those formats.  Any unexpected value most likely
65        // indicates that the disk image is unformatted.
66        let dos_type = Id::from_bytes(&block[format.dos_type_offset..format.dos_type_offset + 2]);
67        if dos_type != format.expected_dos_type {
68            return Err(DiskError::InvalidHeader.into());
69        }
70
71        // We only handle disks with the standard CBM DOS version for its
72        // particular format (e.g. 0x41 "A" for 1541, 0x44 "D" for 1581, etc.).
73        // (For whatever it's worth, the sample "edit disk name" program on page 78
74        // of "Inside Commodore DOS" also performs this check.)
75        let disk_dos_version_type = block[format.disk_dos_version_offset];
76        if disk_dos_version_type != format.expected_dos_version {
77            return Err(DiskError::InvalidHeader.into());
78        }
79
80        // All 1571 disk images should have the double-side flag set.
81        if let Some((offset, value)) = format.double_sided_flag_expectation {
82            if block[offset] != value {
83                return Err(DiskError::InvalidHeader.into());
84            }
85        }
86
87        Ok(Header {
88            first_directory_sector: Location::from_bytes(&block[format.first_directory_offset..]),
89            disk_dos_version_type,
90            disk_name: Petscii::from_bytes(
91                &block[format.disk_name_offset..format.disk_name_offset + disk::DISK_NAME_SIZE],
92            ),
93            disk_id: Id::from_bytes(&block[format.disk_id_offset..format.disk_id_offset + 2]),
94            dos_type,
95            geos: GEOSDiskHeader::new(block),
96        })
97    }
98
99    /// Write the header to the provided block buffer.  This function only
100    /// writes into the regions corresponding to the fields we know about,
101    /// thus allowing the preservation of any non-standard fields if the
102    /// caller provides the current header block.
103    pub fn write(&mut self, blocks: BlockDeviceRef, format: &HeaderFormat) -> io::Result<()> {
104        // Read the header block
105        let mut block = blocks.borrow().sector(format.location)?.to_vec();
106
107        // Render our header struct into the block
108        {
109            self.first_directory_sector
110                .to_bytes(&mut block[format.first_directory_offset..]);
111            block[format.disk_dos_version_offset] = self.disk_dos_version_type;
112            self.disk_name
113                .write_bytes_with_padding(
114                    &mut block
115                        [format.disk_name_offset..format.disk_name_offset + disk::DISK_NAME_SIZE],
116                    PADDING_BYTE,
117                )
118                .map_err(|_| {
119                    let e: io::Error = DiskError::FilenameTooLong.into();
120                    e
121                })?;
122            block[format.disk_id_offset] = self.disk_id[0];
123            block[format.disk_id_offset + 1] = self.disk_id[1];
124            block[format.dos_type_offset] = self.dos_type[0];
125            block[format.dos_type_offset + 1] = self.dos_type[1];
126        }
127
128        // All 1571 disk images should have the double-side flag set.
129        if let Some((offset, value)) = format.double_sided_flag_expectation {
130            block[offset] = value;
131        }
132
133        // Headers should have certain bytes set to the padding byte (0xA0).  If this
134        // is not done, directory listings generated by CBM DOS will be garbled
135        // on the "blocks free" line.
136        for padding_offset in format.padding_offsets {
137            block[*padding_offset as usize] = disk::PADDING_BYTE;
138        }
139
140        // Write the header block
141        blocks
142            .borrow_mut()
143            .sector_mut(format.location)?
144            .copy_from_slice(&block);
145        Ok(())
146    }
147}
148
149impl fmt::Debug for Header {
150    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151        writeln!(f, "disk name: {:?}", self.disk_name)?;
152        writeln!(f, "disk id: {:?}", self.disk_id)?;
153        writeln!(f, "dos type: {:?}", self.dos_type)?;
154        writeln!(
155            f,
156            "format: {}",
157            match self.geos {
158                Some(ref geos) => geos.id.to_escaped_string(),
159                None => "CBM".to_string(),
160            }
161        )
162    }
163}