1use log::{debug, error, info};
3
4use std::{
5 cmp::min,
6 fs::{self, File},
7 io::Write,
8 path::{Path, PathBuf},
9};
10
11use nom::bytes::complete::take;
12use nom::multi::count;
13use nom::number::complete::{le_i8, le_u16, le_u8};
14use nom::{Err, IResult};
15
16use std::fmt::{Display, Formatter, Result};
17
18use crate::config::Config;
19use crate::disk_format::apple::catalog::{build_files, parse_catalogs, Files, FullCatalog};
20use crate::disk_format::apple::nibble::{parse_nib_disk, recognize_prologue};
21use crate::disk_format::image::{
22 DiskImage, DiskImageGuess, DiskImageGuesser, DiskImageOps, DiskImageParser, DiskImageSaver,
23};
24use crate::disk_format::sanity_check::SanityCheck;
25use crate::error::{Error, ErrorKind, InvalidErrorKind};
26
27use super::nibble::NibbleDisk;
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum Encoding {
32 Plain,
34 Nibble,
36}
37
38impl Display for Encoding {
40 fn fmt(&self, f: &mut Formatter) -> Result {
41 write!(f, "{:?}", self)
42 }
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub enum Format {
48 Unknown(u64),
56 DOS32(u64),
58 DOS33(u64),
60 ProDOS(u64),
62}
63
64impl Display for Format {
66 fn fmt(&self, f: &mut Formatter) -> Result {
67 write!(f, "{:?}", self)
68 }
69}
70
71pub struct VolumeTableOfContents<'a> {
74 pub reserved: u8,
76
77 pub track_number_of_first_catalog_sector: u8,
79 pub sector_number_of_first_catalog_sector: u8,
81
82 pub release_number_of_dos: u8,
84 pub reserved2: &'a [u8],
87 pub diskette_volume_number: u8,
89 pub reserved3: &'a [u8],
92
93 pub maximum_number_of_track_sector_pairs: u8,
96
97 pub reserved4: &'a [u8],
100
101 pub last_track_where_sectors_were_allocated: u8,
103 pub direction_of_track_allocation: i8,
105
106 pub reserved5: &'a [u8],
109
110 pub number_of_tracks_per_diskette: u8,
112 pub number_of_sectors_per_track: u8,
114 pub number_of_bytes_per_sector: u16,
116
117 pub bit_map_of_free_sectors: Vec<&'a [u8]>,
121}
122
123impl Display for VolumeTableOfContents<'_> {
125 fn fmt(&self, f: &mut Formatter) -> Result {
126 writeln!(
127 f,
128 "track number of first catalog sector: {}",
129 self.track_number_of_first_catalog_sector
130 )?;
131 writeln!(
132 f,
133 "sector number of first catalog sector: {}",
134 self.sector_number_of_first_catalog_sector
135 )?;
136 writeln!(f, "release number of DOS: {}", self.release_number_of_dos)?;
137 writeln!(f, "diskette volume number: {}", self.diskette_volume_number)?;
138
139 writeln!(
140 f,
141 "number of tracks per diskette: {}",
142 self.number_of_tracks_per_diskette
143 )?;
144 writeln!(
145 f,
146 "number of sectors per track: {}",
147 self.number_of_sectors_per_track
148 )?;
149 writeln!(
150 f,
151 "number of bytes per sector: {}",
152 self.number_of_bytes_per_sector
153 )?;
154 writeln!(
155 f,
156 "last_track_where_sectors_were_allocated: {}",
157 self.last_track_where_sectors_were_allocated
158 )
159 }
160}
161
162pub fn parse_volume_table_of_contents(i: &[u8]) -> IResult<&[u8], VolumeTableOfContents<'_>> {
164 let (i, reserved) = le_u8(i)?;
165 let (i, track_number_of_first_catalog_sector) = le_u8(i)?;
166 let (i, sector_number_of_first_catalog_sector) = le_u8(i)?;
167 let (i, release_number_of_dos) = le_u8(i)?;
168 let (i, reserved2) = take(2_usize)(i)?;
169 let (i, diskette_volume_number) = le_u8(i)?;
170 let (i, reserved3) = take(32_usize)(i)?;
171 let (i, maximum_number_of_track_sector_pairs) = le_u8(i)?;
172 let (i, reserved4) = take(8_usize)(i)?;
173 let (i, last_track_where_sectors_were_allocated) = le_u8(i)?;
174 let (i, direction_of_track_allocation) = le_i8(i)?;
175 let (i, reserved5) = take(2_usize)(i)?;
176 let (i, number_of_tracks_per_diskette) = le_u8(i)?;
177 let (i, number_of_sectors_per_track) = le_u8(i)?;
178 let (i, number_of_bytes_per_sector) = le_u16(i)?;
179
180 let bit_maps_to_read = min(number_of_tracks_per_diskette, 50);
183
184 let (i, bit_map_of_free_sectors) = count(take(4_usize), bit_maps_to_read.into())(i)?;
185
186 Ok((
187 i,
188 VolumeTableOfContents {
189 reserved,
190 track_number_of_first_catalog_sector,
191 sector_number_of_first_catalog_sector,
192 release_number_of_dos,
193 reserved2,
194 diskette_volume_number,
195 reserved3,
196 maximum_number_of_track_sector_pairs,
197 reserved4,
198 last_track_where_sectors_were_allocated,
199 direction_of_track_allocation,
200 reserved5,
201 number_of_tracks_per_diskette,
202 number_of_sectors_per_track,
203 number_of_bytes_per_sector,
204 bit_map_of_free_sectors,
205 },
206 ))
207}
208
209impl SanityCheck for VolumeTableOfContents<'_> {
210 fn check(&self) -> bool {
211 if (self.number_of_tracks_per_diskette != 35) && (self.number_of_tracks_per_diskette != 40)
212 {
213 debug!(
214 "Suspicious number of tracks per diskette: {}",
215 self.number_of_tracks_per_diskette
216 );
217 return false;
218 }
219
220 if (self.number_of_sectors_per_track != 13) && (self.number_of_sectors_per_track != 16) {
221 debug!(
222 "Suspicious number of sectors per track: {}",
223 self.number_of_sectors_per_track
224 );
225 return false;
226 }
227
228 true
229 }
230}
231
232pub struct AppleDOSDisk<'a> {
234 pub volume_table_of_contents: VolumeTableOfContents<'a>,
236 pub catalog: FullCatalog<'a>,
238 pub tracks: Vec<Vec<&'a [u8]>>,
242
243 pub files: Files<'a>,
245}
246
247#[allow(clippy::large_enum_variant)]
254pub enum AppleDiskData<'a> {
255 DOS(AppleDOSDisk<'a>),
257 ProDOS,
259 Nibble(NibbleDisk),
261}
262
263impl DiskImageSaver for AppleDOSDisk<'_> {
264 fn save_disk_image(
265 &self,
266 _config: &Config,
267 selected_filename: Option<&str>,
268 filename: &str,
269 ) -> std::result::Result<(), crate::error::Error> {
270 if selected_filename.is_none() {
271 error!("Filename must be specified for saving Apple DOS 3.3 images");
272 return Err(crate::error::Error::new(ErrorKind::Message(String::from(
273 "Filename must be specified for saving Apple DOS 3.3 images",
274 ))));
275 }
276 let selected_filename = selected_filename.unwrap();
277 let filename = PathBuf::from(filename);
278 let file_result = File::create(filename);
279 match file_result {
280 Ok(mut file) => {
281 let selected_file = self.files.get(selected_filename).unwrap();
282
283 file.write_all(&selected_file.data)?;
284 }
285 Err(e) => error!("Error opening file: {}", e),
286 }
287 Ok(())
288 }
289}
290
291pub struct AppleDisk<'a> {
293 pub encoding: Encoding,
295 pub format: Format,
297
298 pub data: AppleDiskData<'a>,
300}
301
302impl Display for AppleDisk<'_> {
304 fn fmt(&self, f: &mut Formatter) -> Result {
305 write!(f, "encoding: {}, format: {}", self.encoding, self.format)
306 }
307}
308
309#[derive(Clone, Copy, Debug, Eq, PartialEq)]
311pub struct AppleDiskGuess<'a> {
312 pub encoding: Encoding,
314 pub format: Format,
316 pub data: &'a [u8],
318}
319
320impl<'a> AppleDiskGuess<'a> {
321 pub fn new(encoding: Encoding, format: Format, data: &'a [u8]) -> AppleDiskGuess<'a> {
324 AppleDiskGuess {
325 encoding,
326 format,
327 data,
328 }
329 }
330}
331
332impl Display for AppleDiskGuess<'_> {
334 fn fmt(&self, f: &mut Formatter) -> Result {
335 write!(f, "encoding: {}, format: {}", self.encoding, self.format)
336 }
337}
338
339impl<'a, 'b> DiskImageGuesser<'a, 'b> for AppleDiskGuess<'a> {
341 fn guess(
342 _config: &crate::config::Config,
343 filename: &str,
344 data: &'a [u8],
345 ) -> Option<DiskImageGuess<'a>> {
346 let filename_extension: Vec<_> = filename.split('.').collect();
347 let path = Path::new(&filename);
348
349 let filesize = match fs::metadata(path) {
350 Ok(metadata) => metadata.len(),
351 Err(e) => {
352 error!("Couldn't get file metadata: {}", e);
353 panic!("Couldn't get file metadata");
354 }
355 };
356
357 match filename_extension[filename_extension.len() - 1]
358 .to_lowercase()
359 .as_str()
360 {
361 "do" => Some(DiskImageGuess::Apple(AppleDiskGuess::new(
364 Encoding::Plain,
365 Format::DOS33(filesize),
366 data,
367 ))),
368 "dsk" => Some(DiskImageGuess::Apple(AppleDiskGuess::new(
369 Encoding::Plain,
370 Format::DOS33(filesize),
371 data,
372 ))),
373 "nib" => {
374 let prologue_byte_result = recognize_prologue(data);
375 let format = match prologue_byte_result {
376 Some(r) => match r {
377 0xB5 => Format::DOS32(filesize),
378 0x96 => Format::DOS33(filesize),
379 _ => Format::Unknown(filesize),
380 },
381 None => Format::Unknown(filesize),
382 };
383
384 Some(DiskImageGuess::Apple(AppleDiskGuess::new(
385 Encoding::Nibble,
386 format,
387 data,
388 )))
389 }
390 &_ => {
391 let res = format_from_data(data);
393 match res {
394 Err(_) => {
395 info!("Couldn't detect disk type");
396 None
397 }
398 Ok(s) => s,
399 }
400 }
401 }
402 }
403
404 fn parse(
405 &'b self,
406 config: &'a crate::config::Config,
407 ) -> std::result::Result<DiskImage<'a>, Error> {
408 let result = apple_disk_parser(self, config);
409 match result {
410 Ok(res) => Ok(DiskImage::Apple(res)),
411 Err(e) => Err(Error::new(ErrorKind::Invalid(InvalidErrorKind::Invalid(
412 nom::Err::Error(e).to_string(),
413 )))),
414 }
415 }
416}
417
418pub fn format_from_data(data: &[u8]) -> std::result::Result<Option<DiskImageGuess<'_>>, Error> {
435 let filesize: u64 = data.len().try_into().unwrap();
436
437 info!("Reading magic number from file");
438 let (_i, header) = take(0x09_usize)(data)?;
439
440 if header != [0x01, 0xA5, 0x27, 0xC9, 0x09, 0xD0, 0x18, 0xA5, 0x2B] {
441 return Ok(None);
442 }
443 let (i, _junk) = take(0x11000_usize)(data)?;
445 let (i, _reserved) = le_u8(i)?;
446 let (i, track_number_of_first_catalog_sector) = le_u8(i)?;
447 let (i, sector_number_of_first_catalog_sector) = le_u8(i)?;
448 let (_i, release_number_of_dos) = le_u8(i)?;
449
450 if (track_number_of_first_catalog_sector == 0x11)
451 && (sector_number_of_first_catalog_sector == 0x0F)
452 && (release_number_of_dos == 0x03)
453 {
454 info!("Found Apple DOS 3.3 disk");
455 Ok(Some(DiskImageGuess::Apple(AppleDiskGuess::new(
456 Encoding::Plain,
457 Format::DOS33(filesize),
458 data,
459 ))))
460 } else {
461 Ok(None)
462 }
463}
464
465pub fn apple_tracks_parser(
467 track_size: usize,
468 number_of_tracks: usize,
469) -> impl Fn(&[u8]) -> IResult<&[u8], Vec<&[u8]>> {
470 move |i| count(take(track_size), number_of_tracks)(i)
471}
472
473pub fn apple_140_k_dos_parser(i: &[u8], tracks_per_disk: usize) -> IResult<&[u8], Vec<&[u8]>> {
482 if tracks_per_disk == 35 {
483 apple_tracks_parser(4096, 35)(i)
484 } else if tracks_per_disk == 40 {
485 apple_tracks_parser(3584, 40)(i)
486 } else {
487 Err(Err::Error(nom::error::Error::new(
488 i,
489 nom::error::ErrorKind::Fail,
490 )))
491 }
492}
493
494pub fn volume_parser(i: &[u8], filesize: u64) -> IResult<&[u8], AppleDisk<'_>> {
496 let tracks_per_disk = 35;
498
499 let catalog_sector_start = 17;
504
505 let (_i, raw_tracks) = apple_140_k_dos_parser(i, tracks_per_disk)?;
510
511 let (i, vtoc) = parse_volume_table_of_contents(raw_tracks[catalog_sector_start])?;
523
524 debug!("VTOC: {}", vtoc);
525
526 if !vtoc.check() {
527 error!("Invalid data");
528 return Err(Err::Error(nom::error::Error::new(
529 i,
530 nom::error::ErrorKind::Fail,
531 )));
532 }
533
534 let mut tracks: Vec<Vec<&[u8]>> = Vec::new();
535
536 let catalog_sector = raw_tracks[catalog_sector_start][2];
541
542 for track in raw_tracks {
543 let mut track_vec: Vec<&[u8]> = Vec::new();
544 let (_i, sectors) = count(take(256_usize), 16)(track)?;
545 for sector in sectors {
546 track_vec.push(sector);
547 }
548 tracks.push(track_vec);
549 }
550
551 let catalog_res = parse_catalogs(
552 &tracks,
553 catalog_sector_start.try_into().unwrap(),
554 catalog_sector,
555 );
556 let catalog = match catalog_res {
557 Ok(catalog) => catalog,
558 Err(_e) => {
559 return Err(Err::Error(nom::error::Error::new(
560 i,
561 nom::error::ErrorKind::Fail,
562 )));
563 }
564 };
565
566 debug!("Catalog:\n{}", catalog);
567
568 let files = build_files(catalog.clone(), &tracks).unwrap();
570
571 let apple_dos_disk = AppleDOSDisk {
572 volume_table_of_contents: vtoc,
573 catalog,
574 tracks,
575 files,
576 };
577
578 Ok((
579 i,
580 AppleDisk {
581 encoding: Encoding::Plain,
582 format: Format::DOS33(filesize),
583 data: AppleDiskData::DOS(apple_dos_disk),
584 },
585 ))
586}
587
588pub fn apple_disk_parser<'a>(
590 guess: &AppleDiskGuess<'a>,
591 config: &Config,
592) -> core::result::Result<AppleDisk<'a>, Error> {
593 debug!("Parsing based on guess: {}", guess);
594
595 match guess.encoding {
596 Encoding::Plain => {
597 let filesize = if let Format::DOS33(size) = guess.format {
598 size
599 } else {
600 0
601 };
602
603 if filesize == 143360 {
604 let res = volume_parser(guess.data, filesize);
605
606 match res {
607 Ok(ad) => Ok(ad.1),
608 Err(e) => Err(Error::new(ErrorKind::Invalid(InvalidErrorKind::Invalid(
609 e.to_string(),
610 )))),
611 }
612 } else {
613 Err(Error::new(ErrorKind::Invalid(InvalidErrorKind::Invalid(
614 String::from("Test1"),
615 ))))
616 }
617 }
618 Encoding::Nibble => {
619 debug!("Parsing as nibble format");
620 let (_i, disk) = parse_nib_disk(config)(guess.data)?;
621
622 Ok(AppleDisk {
623 encoding: guess.encoding,
624 format: guess.format,
625 data: AppleDiskData::Nibble(disk),
626 })
627 }
628 }
629}
630
631impl<'a> DiskImageParser<'a, '_> for AppleDiskGuess<'a> {
633 fn parse_disk_image(
634 &'a self,
635 config: &'a crate::config::Config,
636 _filename: &str,
637 ) -> std::result::Result<DiskImage<'a>, Error> {
638 info!("DiskImageParser Attempting to parse Apple disk");
639 let result = apple_disk_parser(self, config);
640 match result {
641 Ok(apple_disk) => Ok(DiskImage::Apple(apple_disk)),
642 Err(e) => Err(Error::new(ErrorKind::Invalid(InvalidErrorKind::Invalid(
643 nom::Err::Error(e).to_string(),
644 )))),
645 }
646 }
647}
648
649impl<'a, 'b> DiskImageOps<'a, 'b> for AppleDiskGuess<'a> {
650 fn catalog(&'a self, _config: &'b crate::config::Config) -> std::result::Result<String, Error> {
651 info!("DiskImageParser catalog unimplemented for Apple disk");
652 Err(Error::new(ErrorKind::Invalid(InvalidErrorKind::Invalid(
653 String::from("DiskImageParser catalog unimplemented for Apple disk"),
654 ))))
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use std::fs::OpenOptions;
661 use std::io::Write;
662 use std::path::Path;
663
664 use crate::disk_format::image::{format_from_filename_and_data, DiskImageGuess};
665
666 use super::{
667 apple_disk_parser, format_from_data, parse_volume_table_of_contents, AppleDiskData,
668 AppleDiskGuess, Encoding, Format,
669 };
670 use crate::config::{Config, Configuration};
671
672 const VTOC_DATA: [u8; 256] = [
673 0x00, 0x11, 0x0F, 0x03, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
674 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
675 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00,
676 0x00, 0x00, 0x00, 0x12, 0x01, 0x00, 0x00, 0x23, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
677 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00,
678 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF,
679 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF,
680 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
681 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00,
682 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF,
683 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF,
684 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
685 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00,
686 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
687 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
688 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
689 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
690 0x00,
691 ];
692
693 #[test]
695 fn format_from_filename_works() {
696 let filename = "testdata/test-disk_format_from_filename_works.dsk";
697
698 let path = Path::new(&filename);
701 let mut file = OpenOptions::new()
702 .create(true)
703 .truncate(true)
704 .write(true)
705 .open(path)
706 .unwrap_or_else(|e| {
707 panic!("Couldn't open file: {}", e);
708 });
709 let data: [u8; 143360] = [0; 143360];
710
711 file.write_all(&data).unwrap_or_else(|e| {
712 panic!("Error writing test file: {}", e);
713 });
714 file.flush().unwrap_or_else(|e| {
715 panic!("Couldn't flush file stream: {}", e);
716 });
717
718 let config = Config::load(config::Config::default()).unwrap();
719 let guess = format_from_filename_and_data(&config, filename, &data).unwrap_or_else(|| {
720 panic!("Invalid filename guess");
721 });
722
723 assert_eq!(
724 guess,
725 DiskImageGuess::Apple(AppleDiskGuess::new(
726 Encoding::Plain,
727 Format::DOS33(143360),
728 &data
729 ))
730 );
731
732 std::fs::remove_file(filename).unwrap_or_else(|e| {
733 panic!("Error removing test file: {}", e);
734 });
735 }
736
737 #[test]
741 fn format_from_data_works() {
742 let mut data: [u8; 143360] = [0; 143360];
743 let magic_number_start = [0x01, 0xA5, 0x27, 0xC9, 0x09, 0xD0, 0x18, 0xA5, 0x2B];
744 let magic_number_vtoc = [0x11, 0x0F, 0x03];
745
746 data[0..9].copy_from_slice(&magic_number_start);
747 data[0x11001..0x11004].copy_from_slice(&magic_number_vtoc);
748
749 let guess_res = format_from_data(&data);
750 match guess_res {
751 Ok(g) => {
752 if let Some(guess) = g {
753 assert_eq!(
754 guess,
755 DiskImageGuess::Apple(AppleDiskGuess::new(
756 Encoding::Plain,
757 Format::DOS33(143360),
758 &data
759 ))
760 );
761 } else {
762 panic!("Invalid data guess");
763 }
764 }
765 Err(_) => {
766 panic!("Data guess failed");
767 }
768 };
769 }
770
771 #[test]
782 fn format_from_data_fails() {
783 let mut data: [u8; 143360] = [0; 143360];
784 let magic_number_start = [0x01, 0xA5, 0x27, 0xC9, 0x09, 0xD0, 0x18, 0xA5, 0x2B];
785
786 data[0..9].copy_from_slice(&magic_number_start);
787
788 let guess_res = format_from_data(&data);
789 match guess_res {
790 Ok(g) => {
791 if let Some(guess) = g {
792 assert_ne!(
793 guess,
794 DiskImageGuess::Apple(AppleDiskGuess::new(
795 Encoding::Plain,
796 Format::DOS33(143360),
797 &data
798 ))
799 );
800 } else {
801 assert_eq!(g, None, "Correct data guess for invalid DOS 3.3 data");
802 }
803 }
804 Err(_) => {
805 panic!("Data guess failed");
806 }
807 };
808 }
809
810 #[test]
812 fn parse_volume_table_of_contents_works() {
813 let vtoc_data = VTOC_DATA;
814
815 let result = parse_volume_table_of_contents(&vtoc_data);
816
817 match result {
818 Ok(vtoc) => {
819 assert_eq!(vtoc.1.track_number_of_first_catalog_sector, 17);
820 assert_eq!(vtoc.1.sector_number_of_first_catalog_sector, 15);
821 assert_eq!(vtoc.1.release_number_of_dos, 3);
822 assert_eq!(vtoc.1.diskette_volume_number, 254);
823 assert_eq!(vtoc.1.number_of_tracks_per_diskette, 35);
824 assert_eq!(vtoc.1.number_of_sectors_per_track, 16);
825 assert_eq!(vtoc.1.number_of_bytes_per_sector, 256);
826 assert_eq!(vtoc.1.last_track_where_sectors_were_allocated, 18);
827 }
828 Err(e) => {
829 panic!("Couldn't parse VTOC: {}", e);
830 }
831 }
832 }
833
834 #[test]
838 fn apple_disk_parser_disk_works() {
839 let filename = "testdata/test-apple_disk_parser_works.dsk";
840 let path = Path::new(&filename);
841
842 let mut data: Vec<u8> = Vec::new();
843 let data_prefix: [u8; 0x11000] = [0; 0x11000];
844 let data_vtoc = VTOC_DATA;
845 let data_suffix: [u8; 0x11F00] = [0; 0x11F00];
846
847 data.extend(data_prefix);
848 data.extend(data_vtoc);
849 data.extend(data_suffix);
850
851 std::fs::write(path, &data).unwrap_or_else(|e| {
852 panic!("Error writing test file: {}", e);
853 });
854
855 let guess = AppleDiskGuess::new(Encoding::Plain, Format::DOS33(143360), &data);
862
863 let config = Config::load(config::Config::default()).unwrap();
864 let res = apple_disk_parser(&guess, &config);
865
866 match res {
867 Ok(disk) => match disk.data {
868 AppleDiskData::DOS(apple_dos_disk) => {
869 let vtoc = apple_dos_disk.volume_table_of_contents;
870
871 assert_eq!(disk.encoding, Encoding::Plain);
872 assert_eq!(disk.format, Format::DOS33(143360));
873 assert_eq!(vtoc.track_number_of_first_catalog_sector, 17);
874 assert_eq!(vtoc.sector_number_of_first_catalog_sector, 15);
875 assert_eq!(vtoc.release_number_of_dos, 3);
876 assert_eq!(vtoc.diskette_volume_number, 254);
877 assert_eq!(vtoc.number_of_tracks_per_diskette, 35);
878 assert_eq!(vtoc.number_of_sectors_per_track, 16);
879 assert_eq!(vtoc.number_of_bytes_per_sector, 256);
880 assert_eq!(vtoc.last_track_where_sectors_were_allocated, 18);
881 }
882 _ => {
883 panic!("Invalid format");
884 }
885 },
886 Err(_e) => {
887 panic!("This should have succeeded");
888 }
889 }
890
891 std::fs::remove_file(filename).unwrap_or_else(|e| {
892 panic!("Error removing test file: {}", e);
893 });
894 }
895
896 #[test]
900 fn apple_disk_parser_nonstandard_disk_panics() {
901 let filename = "testdata/test-apple_disk_parser_nonstandard_disk_panics.dsk";
902
903 let path = Path::new(&filename);
906 let data: [u8; 143360] = [0; 143360];
907 std::fs::write(path, data).unwrap_or_else(|e| {
908 panic!("Error writing test file: {}", e);
909 });
910
911 let guess = AppleDiskGuess::new(Encoding::Plain, Format::DOS33(143360), &data);
912
913 let config = Config::load(config::Config::default()).unwrap();
914 let res = apple_disk_parser(&guess, &config);
915
916 match res {
917 Ok(_disk) => {
918 panic!("This should have failed parsing");
919 }
920 Err(_e) => {
921 assert_eq!(true, true);
923 }
924 }
925
926 std::fs::remove_file(filename).unwrap_or_else(|e| {
927 panic!("Error removing test file: {}", e);
928 });
929 }
930}