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
11pub struct HeaderFormat {
14 pub location: Location,
15 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 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 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 pub fn read(blocks: BlockDeviceRef, format: &HeaderFormat) -> io::Result<Header> {
58 let blocks = blocks.borrow();
59 let block = blocks.sector(format.location)?;
60
61 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 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 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 pub fn write(&mut self, blocks: BlockDeviceRef, format: &HeaderFormat) -> io::Result<()> {
104 let mut block = blocks.borrow().sector(format.location)?.to_vec();
106
107 {
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 if let Some((offset, value)) = format.double_sided_flag_expectation {
130 block[offset] = value;
131 }
132
133 for padding_offset in format.padding_offsets {
137 block[*padding_offset as usize] = disk::PADDING_BYTE;
138 }
139
140 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}