1use log::debug;
3
4use nom::error::ErrorKind;
5use nom::{Err, IResult};
6
7use nom::bytes::complete::take;
8use nom::multi::count;
9use nom::number::complete::{le_u16, le_u8};
10
11use std::{
12 collections::HashMap,
13 fmt::{Display, Formatter, Result},
14 string::FromUtf8Error,
15};
16
17use crate::serialize::{little_endian_word_to_bytes, Serializer};
18
19#[derive(Clone, Copy, Debug)]
21pub enum FileType {
22 Text = 0,
24 IntegerBasic = 1,
26 AppleSoftBasic = 2,
28 Binary = 4,
30 SType = 8,
32 RelocatableObjectModule = 10,
34 AType = 20,
36 BType = 40,
38 Unknown,
40}
41
42impl Display for FileType {
44 fn fmt(&self, f: &mut Formatter) -> Result {
45 match self {
46 FileType::Text => write!(f, "T"),
47 FileType::IntegerBasic => write!(f, "I"),
48 FileType::AppleSoftBasic => write!(f, "A"),
49 FileType::Binary => write!(f, "B"),
50 FileType::SType => write!(f, "S"),
51 FileType::RelocatableObjectModule => write!(f, "R"),
52 FileType::AType => write!(f, "AT"),
53 FileType::BType => write!(f, "BT"),
54 FileType::Unknown => write!(f, "U"),
55 }
56 }
57}
58
59#[derive(Clone, Copy, Debug)]
61pub struct FileEntry<'a> {
62 pub track_of_first_track_sector_list_sector: u8,
64 pub sector_of_first_track_sector_list_sector: u8,
66
67 pub file_type: FileType,
69
70 pub locked: bool,
72
73 pub file_name: &'a [u8],
75
76 pub file_length_in_sectors: u16,
78}
79
80pub struct File<'a> {
83 track_sector_lists: TrackSectorLists<'a>,
85
86 pub data: Vec<u8>,
88}
89
90impl Display for File<'_> {
91 fn fmt(&self, f: &mut Formatter) -> Result {
92 for tsl in &self.track_sector_lists {
93 writeln!(f, "track_sector_list: {}", tsl)?;
94 }
95 writeln!(f, "length of data: {}", self.data.len())
96 }
97}
98
99pub type Files<'a> = HashMap<String, File<'a>>;
101
102#[derive(Clone)]
106pub struct TrackSectorList<'a> {
107 pub reserved: u8,
109 pub track_number_of_next_sector: Option<u8>,
112 pub sector_number_of_next_sector: Option<u8>,
115 pub reserved_2: &'a [u8],
117
118 pub sector_offset_in_file: &'a [u8],
121
122 pub reserved_3: &'a [u8],
124
125 pub track_sector_pairs: TrackSectorPairs, }
128
129impl Display for TrackSectorList<'_> {
131 fn fmt(&self, f: &mut Formatter) -> Result {
132 write!(f, "reserved: {}", self.reserved)?;
133 match self.track_number_of_next_sector {
134 Some(x) => {
135 writeln!(f, "track_number_of_next_sector: 0x{:02X}", x)?;
136 }
137 None => {
138 writeln!(f, "track_number_of_next_sector: None")?;
139 }
140 }
141 match self.sector_number_of_next_sector {
142 Some(x) => {
143 writeln!(f, "sector_number_of_next_sector: 0x{:02X}, ", x)?;
144 }
145 None => {
146 writeln!(f, "sector_number_of_next_sector: None")?;
147 }
148 }
149 write!(f, "reserved_2: {:?}", self.reserved_2)?;
150 writeln!(f, "Track Sector Pairs:")?;
151 for tsp in &self.track_sector_pairs {
152 writeln!(f, "track_sector_pair: {}", tsp)?;
153 }
154 writeln!(f)
155 }
156}
157
158impl<'a> Serializer<'a> for TrackSectorList<'a> {
159 fn as_vec(&'a self) -> std::result::Result<Vec<u8>, crate::error::Error> {
160 let mut bytes = Vec::new();
161
162 bytes.push(self.reserved);
163 if let Some(track_number) = self.track_number_of_next_sector {
165 bytes.push(track_number);
166 } else {
167 bytes.push(0);
168 }
169 if let Some(sector_number) = self.sector_number_of_next_sector {
170 bytes.push(sector_number);
171 } else {
172 bytes.push(0);
173 }
174
175 bytes.append(&mut self.reserved_2.to_vec());
176 bytes.append(&mut self.sector_offset_in_file.to_vec());
177 bytes.append(&mut self.reserved_3.to_vec());
178 bytes.append(&mut self.track_sector_pairs.as_vec().unwrap());
179
180 Ok(bytes)
181 }
182}
183
184pub type TrackSectorLists<'a> = Vec<TrackSectorList<'a>>;
187
188pub fn parse_track_sector_list(i: &[u8]) -> IResult<&[u8], TrackSectorList<'_>> {
190 let mut track_sector_pairs: Vec<TrackSectorPair> = Vec::new();
191
192 let (i, reserved) = le_u8(i)?;
193 let (i, track_number_of_next_sector) = le_u8(i)?;
194 let track_number_of_next_sector = if track_number_of_next_sector != 0 {
196 Some(track_number_of_next_sector)
197 } else {
198 None
199 };
200
201 let (i, sector_number_of_next_sector) = le_u8(i)?;
202 let sector_number_of_next_sector = if sector_number_of_next_sector != 0 {
203 Some(sector_number_of_next_sector)
204 } else {
205 None
206 };
207
208 let (i, reserved_2) = take(2_usize)(i)?;
209 let (i, sector_offset_in_file) = take(2_usize)(i)?;
210 let (i, reserved_3) = take(5_usize)(i)?;
211
212 let (mut i, mut track_sector_pair) = parse_track_sector_pair(i)?;
213
214 let max_tsps = 121;
215 let mut cnt = 1;
216 while (track_sector_pair.track_number != 0) && (cnt <= max_tsps) {
217 track_sector_pairs.push(track_sector_pair);
218 let (i2, tsp) = parse_track_sector_pair(i)?;
219 track_sector_pair = tsp;
220 i = i2;
221 cnt += 1;
222 }
223
224 Ok((
225 i,
226 TrackSectorList {
227 reserved,
228 track_number_of_next_sector,
229 sector_number_of_next_sector,
230 reserved_2,
231 sector_offset_in_file,
232 reserved_3,
233 track_sector_pairs,
234 },
235 ))
236}
237
238#[derive(Clone, Copy, Debug)]
240pub struct TrackSectorPair {
241 pub track_number: u8,
243 pub sector_number: u8,
245}
246
247impl Display for TrackSectorPair {
248 fn fmt(&self, f: &mut Formatter) -> Result {
249 write!(
250 f,
251 "track_number: 0x{:02X}, sector_number: 0x{:02X}",
252 self.track_number, self.sector_number
253 )
254 }
255}
256
257impl<'a> Serializer<'a> for TrackSectorPair {
258 fn as_vec(&'a self) -> std::result::Result<Vec<u8>, crate::error::Error> {
259 let bytes: Vec<u8> = vec![self.track_number, self.sector_number];
260
261 Ok(bytes)
262 }
263}
264
265pub type TrackSectorPairs = Vec<TrackSectorPair>;
267
268impl<'a> Serializer<'a> for TrackSectorPairs {
269 fn as_vec(&'a self) -> std::result::Result<Vec<u8>, crate::error::Error> {
270 let mut bytes: Vec<u8> = Vec::new();
271
272 for tsp in self {
273 bytes.append(&mut tsp.as_vec().unwrap());
274 }
275
276 Ok(bytes)
277 }
278}
279
280pub fn parse_track_sector_pair(i: &[u8]) -> IResult<&[u8], TrackSectorPair> {
282 let (i, track_number) = le_u8(i)?;
283 let (i, sector_number) = le_u8(i)?;
284
285 Ok((
286 i,
287 TrackSectorPair {
288 track_number,
289 sector_number,
290 },
291 ))
292}
293
294impl<'a> FileEntry<'a> {
301 pub fn new(
312 track_of_first_track_sector_list_sector: u8,
313 sector_of_first_track_sector_list_sector: u8,
314 file_type: FileType,
315 locked: bool,
316 filename: &str,
317 file_length_in_sectors: u16,
318 ) -> FileEntry<'_> {
319 FileEntry {
320 track_of_first_track_sector_list_sector,
321 sector_of_first_track_sector_list_sector,
322 file_type,
323 locked,
324 file_name: filename.as_bytes(),
325 file_length_in_sectors,
326 }
327 }
328
329 pub fn filename(&self) -> std::result::Result<String, FromUtf8Error> {
331 let filename_vector: Vec<u8> = self
332 .file_name
333 .iter()
334 .map(|c| if *c > 0x80 { *c - 0x80 } else { *c })
335 .collect();
336 let file_name = String::from_utf8(filename_vector)?;
337
338 let file_name = String::from(file_name.trim_end_matches(' '));
341 Ok(file_name)
342 }
343
344 pub fn get_data(
346 &self,
347 tracks: &[Vec<&[u8]>],
348 track_sector_lists: &TrackSectorLists,
349 ) -> std::result::Result<Vec<u8>, crate::error::Error> {
350 let data: Vec<u8> = track_sector_lists
360 .iter()
361 .flat_map(|tsl| tsl.track_sector_pairs.clone())
362 .flat_map(|tsp| tracks[tsp.track_number as usize][tsp.sector_number as usize])
363 .copied()
364 .collect();
365
366 match self.file_type {
367 FileType::Binary => {
368 if data.len() >= 4 {
369 let (i, address) = le_u16(data.as_slice())?;
370 debug!("Binary file address: {}", address);
371 let (_i, len) = le_u16(i)?;
372 debug!("Binary file length: {}", len);
373 if (data.len() - 4) >= len.into() {
375 Ok(data[4..(len + 4) as usize].to_vec())
376 } else {
377 Ok(data)
378 }
379 } else {
380 Ok(data)
381 }
382 }
383 _ => {
384 let error = crate::error::Error::new(crate::error::ErrorKind::Invalid(
385 crate::error::InvalidErrorKind::Invalid(format!(
386 "Unsupported file type for export: {}",
387 self.file_type
388 )),
389 ));
390 debug!("{}", error);
391 Err(error)
392 }
393 }
394 }
395
396 pub fn build_file(
400 &self,
401 tracks: &[Vec<&'a [u8]>],
402 ) -> std::result::Result<TrackSectorLists<'a>, crate::error::Error> {
403 let mut track_sector_lists: TrackSectorLists = Vec::new();
404
405 let track = self.track_of_first_track_sector_list_sector;
406 let sector = self.sector_of_first_track_sector_list_sector;
407
408 let (_i, track_sector_list) =
410 parse_track_sector_list(tracks[track as usize][sector as usize]).unwrap();
411 track_sector_lists.push(track_sector_list.clone());
412
413 let mut track = track_sector_list.clone().track_number_of_next_sector;
414 let mut sector = track_sector_list.clone().sector_number_of_next_sector;
415 debug!("track sector list: {}", track_sector_lists.first().unwrap());
416
417 while track.is_some() {
418 debug!(
419 "TSList track {}, sector {}",
420 track.unwrap(),
421 sector.unwrap()
422 );
423 let (_i, track_sector_list) =
424 parse_track_sector_list(tracks[track.unwrap() as usize][sector.unwrap() as usize])
425 .unwrap();
426 track = track_sector_list.track_number_of_next_sector;
427 sector = track_sector_list.sector_number_of_next_sector;
428 track_sector_lists.push(track_sector_list);
429 }
430
431 Ok(track_sector_lists)
432 }
433}
434
435impl<'a> Serializer<'a> for FileEntry<'a> {
436 fn as_vec(&'a self) -> std::result::Result<Vec<u8>, crate::error::Error> {
437 let mut bytes: Vec<u8> = Vec::new();
438
439 bytes.push(self.track_of_first_track_sector_list_sector);
440 bytes.push(self.sector_of_first_track_sector_list_sector);
441
442 let file_type = if self.locked {
443 self.file_type as u8 + 0x80
444 } else {
445 self.file_type as u8
446 };
447
448 bytes.push(file_type);
449
450 let num_bytes = self.file_name.len();
451 if (num_bytes == 0) || (num_bytes > 30) {
453 return Err(crate::error::Error::new(crate::error::ErrorKind::Invalid(
454 crate::error::InvalidErrorKind::Invalid(format!(
455 "Filename size is invalid: {}",
456 num_bytes
457 )),
458 )));
459 }
460
461 let mut padding: Vec<u8> = vec![0; 30 - num_bytes];
462
463 padding.fill(0xA0);
464
465 let mut converted_filename: Vec<u8> =
466 self.file_name.to_vec().iter().map(|c| c + 0x80).collect();
467
468 bytes.append(&mut converted_filename);
469 bytes.append(&mut padding);
470 bytes.append(&mut little_endian_word_to_bytes(
471 self.file_length_in_sectors,
472 ));
473
474 Ok(bytes)
475 }
476}
477
478pub struct FullFile<'a> {
481 pub file_entry: FileEntry<'a>,
483 pub data: Vec<u8>,
486 pub address: u16,
488 pub length: u16,
490}
491
492impl<'a> Serializer<'a> for FullFile<'a> {
496 fn as_vec(&'a self) -> std::result::Result<Vec<u8>, crate::error::Error> {
497 let mut bytes: Vec<u8> = Vec::new();
498 match self.file_entry.file_type {
499 FileType::Binary => {
500 bytes.append(&mut little_endian_word_to_bytes(self.address));
501 bytes.append(&mut little_endian_word_to_bytes(self.length));
502 bytes.append(&mut self.data.clone());
504
505 Ok(bytes)
506 }
507 _ => Err(crate::error::Error::new(
508 crate::error::ErrorKind::Unimplemented(format!(
509 "Unsupported file tyep for serialization: {}",
510 self.file_entry.file_type
511 )),
512 )),
513 }
514 }
515}
516
517impl Display for FileEntry<'_> {
519 fn fmt(&self, f: &mut Formatter) -> Result {
520 writeln!(
521 f,
522 "{:>3} {:>3} {} {:>3} {:<30}",
523 self.track_of_first_track_sector_list_sector,
524 self.sector_of_first_track_sector_list_sector,
525 self.file_type,
526 self.file_length_in_sectors,
527 self.filename().unwrap_or_else(|_| String::from("")),
528 )
529 }
530}
531
532pub fn parse_file_entry(i: &[u8]) -> IResult<&[u8], FileEntry<'_>> {
534 let (i, track_of_first_track_sector_list_sector) = le_u8(i)?;
535 let (i, sector_of_first_track_sector_list_sector) = le_u8(i)?;
536
537 let (i, file_type) = le_u8(i)?;
538
539 let locked = (file_type & 0x80) != 0;
543
544 let file_type = match file_type & 0x7F {
545 0 => FileType::Text,
546 1 => FileType::IntegerBasic,
547 2 => FileType::AppleSoftBasic,
548 4 => FileType::Binary,
549 8 => FileType::SType,
550 10 => FileType::RelocatableObjectModule,
551 20 => FileType::AType,
552 40 => FileType::BType,
553 _ => FileType::Unknown,
554 };
555
556 let (i, filename) = take(30_usize)(i)?;
557 let (i, file_length_in_sectors) = le_u16(i)?;
558
559 Ok((
560 i,
561 FileEntry {
562 track_of_first_track_sector_list_sector,
563 sector_of_first_track_sector_list_sector,
564 file_type,
565 locked,
566 file_name: filename,
567 file_length_in_sectors,
568 },
569 ))
570}
571
572pub fn valid_file_entry(i: &[u8]) -> IResult<&[u8], bool> {
575 let (i, res1) = le_u8(i)?;
576 let (i, res2) = le_u8(i)?;
577
578 if (res1 != 0) && (res2 != 0) {
579 Ok((i, true))
580 } else {
581 Err(Err::Error(nom::error_position!(i, ErrorKind::Fail)))
582 }
583}
584
585#[derive(Clone)]
587pub struct Catalog<'a> {
588 pub reserved: u8,
590 pub track_number_of_next_sector: u8,
593 pub sector_number_of_next_sector: u8,
595 pub reserved_2: &'a [u8],
597
598 pub file_entries: Vec<FileEntry<'a>>,
600
601 pub catalog_by_filename: HashMap<String, FileEntry<'a>>,
603}
604
605impl Display for Catalog<'_> {
607 fn fmt(&self, f: &mut Formatter) -> Result {
608 writeln!(
609 f,
610 "track number of next sector: {}",
611 self.track_number_of_next_sector
612 )?;
613 writeln!(
614 f,
615 "sector number of next sector: {}",
616 self.sector_number_of_next_sector
617 )?;
618 for file_entry in &self.file_entries {
619 write!(f, "{}", file_entry)?;
620 }
621 writeln!(f)
622 }
623}
624
625impl<'a> Serializer<'a> for Catalog<'a> {
626 fn as_vec(&'a self) -> std::result::Result<Vec<u8>, crate::error::Error> {
627 let mut v: Vec<u8> = Vec::new();
628
629 v.push(self.reserved);
630 v.push(self.track_number_of_next_sector);
631 v.push(self.sector_number_of_next_sector);
632
633 v.append(&mut self.reserved_2.to_vec());
634 let mut file_entries: Vec<u8> = self
635 .file_entries
636 .iter()
637 .flat_map(|fe| fe.as_vec())
638 .flatten()
639 .collect();
640
641 let padding_len = (7 - self.file_entries.len()) * 35;
642 let mut padding: Vec<u8> = vec![0; padding_len];
643
644 v.append(&mut file_entries);
645 v.append(&mut padding);
646
647 Ok(v)
648 }
649}
650
651pub fn valid_file(track_of_first_track_sector_list_sector: u8) -> bool {
653 (track_of_first_track_sector_list_sector != 0x00)
656 && (track_of_first_track_sector_list_sector != 0xFF)
657}
658
659pub fn parse_catalog(i: &[u8]) -> IResult<&[u8], Catalog<'_>> {
661 let (i, reserved) = le_u8(i)?;
662 let (i, track_number_of_next_sector) = le_u8(i)?;
663 let (i, sector_number_of_next_sector) = le_u8(i)?;
664 let (i, reserved_2) = take(8_usize)(i)?;
665 let (i, file_entries) = count(parse_file_entry, 7)(i)?;
671
672 let file_entries: Vec<FileEntry> = file_entries
673 .iter()
674 .filter(|fe| valid_file(fe.track_of_first_track_sector_list_sector))
675 .copied()
676 .collect();
677
678 let mut catalog_by_filename: HashMap<String, FileEntry> = HashMap::new();
681
682 file_entries.iter().for_each(|fe| {
683 catalog_by_filename.insert(fe.filename().unwrap(), *fe);
684 });
685
686 Ok((
687 i,
688 Catalog {
689 reserved,
690 track_number_of_next_sector,
691 sector_number_of_next_sector,
692 reserved_2,
693 file_entries,
694 catalog_by_filename,
695 },
696 ))
697}
698
699#[derive(Clone, Debug)]
702pub struct FullCatalog<'a> {
703 pub file_entries: Vec<FileEntry<'a>>,
705
706 pub catalog_by_filename: HashMap<String, FileEntry<'a>>,
708}
709
710impl Display for FullCatalog<'_> {
712 fn fmt(&self, f: &mut Formatter) -> Result {
713 for file_entry in &self.file_entries {
714 write!(f, "{}", file_entry)?;
715 }
716 writeln!(f)
717 }
718}
719
720pub fn parse_catalogs<'a>(
723 tracks: &[Vec<&'a [u8]>],
724 catalog_track: u8,
725 catalog_sector: u8,
726) -> std::result::Result<FullCatalog<'a>, crate::error::Error> {
727 let mut file_entries: Vec<FileEntry> = Vec::new();
728 let mut catalog_by_filename: HashMap<String, FileEntry> = HashMap::new();
729
730 let (_i, mut catalog) = parse_catalog(tracks[catalog_track as usize][catalog_sector as usize])?;
731
732 debug!("tracks length: {}", tracks.len());
734 debug!("track one length: {}", tracks[0].len());
735
736 for file in &catalog.file_entries {
738 file_entries.push(*file);
739 catalog_by_filename.insert(file.filename().unwrap(), *file);
740 }
742 while (catalog.track_number_of_next_sector != 0) && (catalog.sector_number_of_next_sector != 0)
748 {
749 let (_i, c) = parse_catalog(
750 tracks[catalog.track_number_of_next_sector as usize]
751 [catalog.sector_number_of_next_sector as usize],
752 )?;
753
754 debug!("parsed another catalog: {}", c);
755
756 catalog = c;
757 for file in &catalog.file_entries {
758 file_entries.push(*file);
759 catalog_by_filename.insert(file.filename().unwrap(), *file);
760 }
761 }
762
763 Ok(FullCatalog {
764 file_entries,
765 catalog_by_filename,
766 })
767}
768
769impl Catalog<'_> {
770 pub fn get_file(&self, filename: &str) -> Vec<u8> {
772 let _file_entry = self.catalog_by_filename.get(filename).unwrap();
773
774 let data: Vec<u8> = Vec::new();
775 data
776 }
777}
778
779pub fn build_files<'a>(
781 catalog: FullCatalog<'a>,
782 tracks: &[Vec<&'a [u8]>],
783) -> std::result::Result<Files<'a>, crate::error::Error> {
784 let mut files: Files = HashMap::new();
785
786 for file_entry in &catalog.file_entries {
787 let track_sector_lists = file_entry.build_file(tracks)?;
788 debug!("Building file: {}", file_entry.filename().unwrap());
789 let res = file_entry.get_data(tracks, &track_sector_lists);
790 let data = res.unwrap_or_default();
791
792 files.insert(
793 file_entry.filename().unwrap(),
794 File {
795 track_sector_lists,
796 data,
797 },
798 );
799 }
800 Ok(files)
801}
802
803#[cfg(test)]
804mod tests {
805 use super::{
806 build_files, parse_catalog, parse_catalogs, parse_file_entry, Catalog, FileEntry, FileType,
807 TrackSectorList, TrackSectorPair, TrackSectorPairs,
808 };
809 use crate::serialize::{little_endian_word_to_bytes, Serializer};
810 use nom::AsBytes;
811 use pretty_assertions::assert_eq;
812 use std::collections::HashMap;
813
814 fn file_entry_as_bytes(
816 file_entry: &FileEntry,
817 ) -> std::result::Result<[u8; 35], crate::error::Error> {
818 Ok(file_entry.as_vec()?.as_bytes().try_into().unwrap())
819 }
820
821 #[test]
823 fn parse_file_entry_works() {
824 let data: [u8; 35] = [
825 0x12, 0x0F, 0x02, 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
826 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
827 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x02, 0x00,
828 ];
829
830 let result = parse_file_entry(&data);
831
832 match result {
833 Ok(file_entry) => {
834 assert_eq!(file_entry.1.track_of_first_track_sector_list_sector, 18);
835 assert_eq!(file_entry.1.sector_of_first_track_sector_list_sector, 15);
836 match file_entry.1.file_type {
837 FileType::AppleSoftBasic => {
838 assert_eq!(true, true);
839 }
840 _ => {
841 panic!("Invalid file type parsed");
842 }
843 }
844 assert!(!file_entry.1.locked);
845 assert_eq!(
846 file_entry.1.file_name,
847 [
848 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
849 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
850 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
851 ]
852 );
853 }
854 Err(e) => {
855 panic!("Error parsing: {}", e);
856 }
857 }
858 }
859
860 #[test]
862 fn parse_file_entry_locked_works() {
863 let data: [u8; 35] = [
864 0x12, 0x0F, 0x82, 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
865 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
866 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x02, 0x00,
867 ];
868
869 let result = parse_file_entry(&data);
870
871 match result {
872 Ok(file_entry) => {
873 assert_eq!(file_entry.1.track_of_first_track_sector_list_sector, 18);
874 assert_eq!(file_entry.1.sector_of_first_track_sector_list_sector, 15);
875 match file_entry.1.file_type {
876 FileType::AppleSoftBasic => {
877 assert_eq!(true, true);
878 }
879 _ => {
880 panic!("Invalid file type parsed");
881 }
882 }
883 assert!(file_entry.1.locked);
884 assert_eq!(
885 file_entry.1.file_name,
886 [
887 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
888 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
889 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
890 ]
891 );
892 }
893 Err(e) => {
894 panic!("Error parsing: {}", e);
895 }
896 }
897 }
898
899 #[test]
901 fn serialize_file_entry_works() {
902 let expected_data: [u8; 35] = [
903 0x12, 0x0F, 0x02, 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
904 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
905 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x02, 0x00,
906 ];
907
908 let data = file_entry_as_bytes(&FileEntry::new(
909 0x12,
910 0x0F,
911 FileType::AppleSoftBasic,
912 false,
913 "HELLO",
914 0x0002,
915 ));
916
917 assert_eq!(data.unwrap(), expected_data);
918 }
919
920 #[test]
922 fn serialize_locked_file_entry_works() {
923 let expected_data: [u8; 35] = [
924 0x12, 0x0F, 0x82, 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
925 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
926 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x02, 0x00,
927 ];
928
929 let data = file_entry_as_bytes(&FileEntry::new(
930 0x12,
931 0x0F,
932 FileType::AppleSoftBasic,
933 true,
934 "HELLO",
935 0x0002,
936 ));
937
938 assert_eq!(data.unwrap(), expected_data);
939 }
940
941 #[test]
945 fn serialize_file_name_len_0_file_entry_fails() {
946 let file_entry = FileEntry::new(0x12, 0x0F, FileType::AppleSoftBasic, false, "", 0x0002);
947
948 let file_entry_as_vec = file_entry.as_vec();
949
950 match file_entry_as_vec {
951 Ok(_) => panic!("Shouldn't be a valid FileEntry"),
952 Err(e) => assert_eq!(
953 e.to_string(),
954 "Image is invalid: Filename size is invalid: 0"
955 ),
956 }
957 }
958
959 #[test]
962 fn serialize_file_name_len_1_file_entry_works() {
963 let expected_data: [u8; 35] = [
964 0x12, 0x0F, 0x02, 0xC8, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
965 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
966 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x02, 0x00,
967 ];
968
969 let data = file_entry_as_bytes(&FileEntry::new(
970 0x12,
971 0x0F,
972 FileType::AppleSoftBasic,
973 false,
974 "H",
975 0x0002,
976 ));
977
978 assert_eq!(data.unwrap(), expected_data);
979 }
980
981 #[test]
984 fn serialize_file_name_len_30_file_entry_works() {
985 let expected_data: [u8; 35] = [
986 0x12, 0x0F, 0x02, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xB0,
987 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4,
988 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0x02, 0x00,
989 ];
990
991 let data = file_entry_as_bytes(&FileEntry::new(
992 0x12,
993 0x0F,
994 FileType::AppleSoftBasic,
995 false,
996 "012345678901234567890123456789",
997 0x0002,
998 ));
999
1000 assert_eq!(data.unwrap(), expected_data);
1001 }
1002
1003 #[test]
1006 fn serialize_file_name_len_31_file_entry_fails() {
1007 let file_entry = FileEntry::new(
1008 0x12,
1009 0x0F,
1010 FileType::AppleSoftBasic,
1011 false,
1012 "0123456789012345678901234567890",
1013 0x0002,
1014 );
1015
1016 let file_entry_as_vec = file_entry.as_vec();
1017
1018 match file_entry_as_vec {
1019 Ok(_) => panic!("Shouldn't be a valid FileEntry"),
1020 Err(e) => assert_eq!(
1021 e.to_string(),
1022 "Image is invalid: Filename size is invalid: 31"
1023 ),
1024 }
1025 }
1026
1027 #[test]
1029 fn file_entry_filename_works() {
1030 let data: [u8; 35] = [
1031 0x12, 0x0F, 0x02, 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
1032 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
1033 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x02, 0x00,
1034 ];
1035
1036 let result = parse_file_entry(&data);
1037
1038 match result {
1039 Ok(file_entry) => match file_entry.1.filename() {
1040 Ok(filename) => {
1041 assert_eq!(filename, "HELLO");
1042 }
1043 Err(e) => {
1044 panic!("Invalid filename: {}", e);
1045 }
1046 },
1047 Err(e) => {
1048 panic!("Error parsing: {}", e);
1049 }
1050 }
1051 }
1052
1053 #[test]
1055 fn serialize_track_sector_pair_works() {
1056 let tsp = TrackSectorPair {
1057 track_number: 0x12,
1058 sector_number: 0x34,
1059 };
1060
1061 let data = tsp.as_vec().unwrap();
1062
1063 assert_eq!(data.len(), 2);
1064 assert_eq!(data[0], 0x12);
1065 assert_eq!(data[1], 0x34);
1066 }
1067
1068 #[test]
1071 fn serialize_track_sector_list_with_zero_track_sector_pair_works() {
1072 let tsl = TrackSectorList {
1073 reserved: 0x01,
1074 track_number_of_next_sector: None,
1075 sector_number_of_next_sector: None,
1076 reserved_2: &[0x02, 0x03],
1077 sector_offset_in_file: &[0x04, 0x05],
1078 reserved_3: &[0x06, 0x07, 0x08, 0x09, 0x10],
1079 track_sector_pairs: Vec::new(),
1080 };
1081
1082 let data = tsl.as_vec().unwrap();
1083
1084 assert_eq!(data.len(), 12);
1085 assert_eq!(data[0], 0x01);
1086 assert_eq!(data[1], 0x00);
1087 assert_eq!(data[2], 0x00);
1088 assert_eq!(data[3], 0x02);
1089 assert_eq!(data[4], 0x03);
1090 assert_eq!(data[5], 0x04);
1091 assert_eq!(data[6], 0x05);
1092 assert_eq!(data[7], 0x06);
1093 assert_eq!(data[8], 0x07);
1094 assert_eq!(data[9], 0x08);
1095 assert_eq!(data[10], 0x09);
1096 assert_eq!(data[11], 0x10);
1097 }
1098
1099 #[test]
1102 fn serialize_track_sector_list_with_one_track_sector_pair_works() {
1103 let tsp = TrackSectorPair {
1104 track_number: 0x12,
1105 sector_number: 0x34,
1106 };
1107 let tsps = Vec::from([tsp]);
1108
1109 let tsl = TrackSectorList {
1110 reserved: 0x01,
1111 track_number_of_next_sector: None,
1112 sector_number_of_next_sector: None,
1113 reserved_2: &[0x02, 0x03],
1114 sector_offset_in_file: &[0x04, 0x05],
1115 reserved_3: &[0x06, 0x07, 0x08, 0x09, 0x10],
1116 track_sector_pairs: tsps,
1117 };
1118
1119 let data = tsl.as_vec().unwrap();
1120
1121 assert_eq!(data.len(), 14);
1122 assert_eq!(data[0], 0x01);
1123 assert_eq!(data[1], 0x00);
1124 assert_eq!(data[2], 0x00);
1125 assert_eq!(data[3], 0x02);
1126 assert_eq!(data[4], 0x03);
1127 assert_eq!(data[5], 0x04);
1128 assert_eq!(data[6], 0x05);
1129 assert_eq!(data[7], 0x06);
1130 assert_eq!(data[8], 0x07);
1131 assert_eq!(data[9], 0x08);
1132 assert_eq!(data[10], 0x09);
1133 assert_eq!(data[11], 0x10);
1134 assert_eq!(data[12], 0x12);
1135 assert_eq!(data[13], 0x34);
1136 }
1137
1138 #[test]
1141 fn serialize_track_sector_list_with_two_track_sector_pair_works() {
1142 let tsp1 = TrackSectorPair {
1143 track_number: 0x12,
1144 sector_number: 0x34,
1145 };
1146 let tsp2 = TrackSectorPair {
1147 track_number: 0x56,
1148 sector_number: 0x78,
1149 };
1150 let tsps = Vec::from([tsp1, tsp2]);
1151
1152 let tsl = TrackSectorList {
1153 reserved: 0x01,
1154 track_number_of_next_sector: None,
1155 sector_number_of_next_sector: None,
1156 reserved_2: &[0x02, 0x03],
1157 sector_offset_in_file: &[0x04, 0x05],
1158 reserved_3: &[0x06, 0x07, 0x08, 0x09, 0x10],
1159 track_sector_pairs: tsps,
1160 };
1161
1162 let data = tsl.as_vec().unwrap();
1163
1164 assert_eq!(data.len(), 16);
1165 assert_eq!(data[0], 0x01);
1166 assert_eq!(data[1], 0x00);
1167 assert_eq!(data[2], 0x00);
1168 assert_eq!(data[3], 0x02);
1169 assert_eq!(data[4], 0x03);
1170 assert_eq!(data[5], 0x04);
1171 assert_eq!(data[6], 0x05);
1172 assert_eq!(data[7], 0x06);
1173 assert_eq!(data[8], 0x07);
1174 assert_eq!(data[9], 0x08);
1175 assert_eq!(data[10], 0x09);
1176 assert_eq!(data[11], 0x10);
1177 assert_eq!(data[12], 0x12);
1178 assert_eq!(data[13], 0x34);
1179 assert_eq!(data[14], 0x56);
1180 assert_eq!(data[15], 0x78);
1181 }
1182
1183 #[test]
1185 fn parse_catalog_one_file_works() {
1186 let data_header: [u8; 46] = [
1188 0x00, 0x11, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x0F, 0x02,
1189 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
1190 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
1191 0xA0, 0xA0, 0x02, 0x00,
1192 ];
1193 let data_footer: [u8; 210] = [0; 210];
1194 let mut data: Vec<u8> = Vec::new();
1195
1196 data.extend(data_header);
1197 data.extend(data_footer);
1198
1199 let result = parse_catalog(&data);
1200
1201 match result {
1202 Ok(catalog) => {
1203 assert_eq!(catalog.1.file_entries.len(), 1);
1204 let file_entry = catalog.1.file_entries.first().unwrap_or_else(|| {
1205 panic!("Error getting file entry");
1206 });
1207 let filename = file_entry.filename().unwrap_or_else(|e| {
1208 panic!("Error getting file name: {}", e);
1209 });
1210 assert_eq!(filename, "HELLO");
1211 }
1212 Err(e) => {
1213 panic!("Error parsing: {}", e);
1214 }
1215 }
1216 }
1217
1218 #[test]
1220 fn parse_catalog_two_files_works() {
1221 let data_header: [u8; 81] = [
1223 0x00, 0x11, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x0F, 0x02,
1224 0xC8, 0xC5, 0xCC, 0xCC, 0xCF, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
1225 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
1226 0xA0, 0xA0, 0x02, 0x00, 0x12, 0x0F, 0x02, 0xC8, 0xC5, 0xCC, 0xD0, 0xA0, 0xA0, 0xA0,
1227 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0,
1228 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x02, 0x00,
1229 ];
1230 let data_footer: [u8; 210] = [0; 210];
1231 let mut data: Vec<u8> = Vec::new();
1232
1233 data.extend(data_header);
1234 data.extend(data_footer);
1235
1236 let result = parse_catalog(&data);
1237
1238 match result {
1239 Ok(catalog) => {
1240 assert_eq!(catalog.1.file_entries.len(), 2);
1241
1242 let file_entry = catalog.1.file_entries.first().unwrap_or_else(|| {
1243 panic!("Error getting file entry");
1244 });
1245 let filename = file_entry.filename().unwrap_or_else(|e| {
1246 panic!("Error getting file name: {}", e);
1247 });
1248 assert_eq!(filename, "HELLO");
1249 let file_entry = catalog
1250 .1
1251 .file_entries
1252 .get(1)
1253 .ok_or_else(|| {
1254 panic!("Error getting file entry");
1255 })
1256 .unwrap_or_else(|_e| {
1257 panic!("Error getting file entry");
1258 });
1259 let filename = file_entry.filename().unwrap_or_else(|e| {
1260 panic!("Error getting file name: {}", e);
1261 });
1262 assert_eq!(filename, "HELP");
1263 }
1264 Err(e) => {
1265 panic!("Error parsing: {}", e);
1266 }
1267 }
1268 }
1269
1270 #[test]
1275 fn parse_single_sector_catalog_works() {
1276 let file_entries_1 = [FileEntry::new(
1277 0x12,
1278 0x0F,
1279 FileType::AppleSoftBasic,
1280 false,
1281 "A",
1282 0x0002,
1283 )];
1284
1285 let mut catalog_by_filename_1: HashMap<String, FileEntry> = HashMap::new();
1286 file_entries_1.iter().for_each(|fe| {
1287 catalog_by_filename_1.insert(fe.filename().unwrap(), *fe);
1288 });
1289
1290 let catalog_1 = Catalog {
1291 reserved: 0x00,
1292 track_number_of_next_sector: 0x00,
1293 sector_number_of_next_sector: 0x00,
1294 reserved_2: &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
1295 file_entries: file_entries_1.to_vec(),
1296 catalog_by_filename: catalog_by_filename_1,
1297 };
1298
1299 let catalog_1_bytes = catalog_1.as_vec().unwrap();
1300
1301 let mut tracks: Vec<Vec<&[u8]>> = Vec::new();
1302
1303 let mut disk_data: [[[u8; 256]; 16]; 35] = [[[0; 256]; 16]; 35];
1304
1305 for (i, byte) in catalog_1_bytes.iter().enumerate() {
1306 disk_data[17][2][i] = *byte;
1307 }
1308
1309 for track in &disk_data {
1310 let mut track_vec: Vec<&[u8]> = Vec::new();
1311 for sector in track {
1312 track_vec.push(sector);
1313 }
1314 tracks.push(track_vec);
1315 }
1316
1317 let catalog = parse_catalogs(&tracks, 17, 2).expect("Should be a valid FullCatalog");
1318 assert_eq!(catalog.file_entries.len(), 1);
1319 assert_eq!(
1320 catalog
1321 .file_entries
1322 .first()
1323 .expect("Should have at least one file")
1324 .filename()
1325 .expect("Should be a valid filename"),
1326 "A"
1327 );
1328 }
1329
1330 #[test]
1332 fn parse_multi_sector_catalog_works() {
1333 let file_entries_1 = [
1334 FileEntry::new(0x12, 0x0F, FileType::AppleSoftBasic, false, "A", 0x0002),
1335 FileEntry::new(0x13, 0x0F, FileType::AppleSoftBasic, false, "B", 0x0002),
1336 FileEntry::new(0x14, 0x0F, FileType::AppleSoftBasic, false, "C", 0x0002),
1337 FileEntry::new(0x15, 0x0F, FileType::AppleSoftBasic, false, "D", 0x0002),
1338 FileEntry::new(0x16, 0x0F, FileType::AppleSoftBasic, false, "E", 0x0002),
1339 FileEntry::new(0x17, 0x0F, FileType::AppleSoftBasic, false, "F", 0x0002),
1340 FileEntry::new(0x18, 0x0F, FileType::AppleSoftBasic, false, "G", 0x0002),
1341 ];
1342 let file_entries_2 = [
1343 FileEntry::new(0x19, 0x0F, FileType::AppleSoftBasic, false, "H", 0x0002),
1344 FileEntry::new(0x1A, 0x0F, FileType::AppleSoftBasic, false, "I", 0x0002),
1345 FileEntry::new(0x1B, 0x0F, FileType::AppleSoftBasic, false, "J", 0x0002),
1346 ];
1347
1348 let mut catalog_by_filename_1: HashMap<String, FileEntry> = HashMap::new();
1349 file_entries_1.iter().for_each(|fe| {
1350 catalog_by_filename_1.insert(fe.filename().unwrap(), *fe);
1351 });
1352
1353 let mut catalog_by_filename_2: HashMap<String, FileEntry> = HashMap::new();
1354 file_entries_2.iter().for_each(|fe| {
1355 catalog_by_filename_2.insert(fe.filename().unwrap(), *fe);
1356 });
1357
1358 let catalog_1 = Catalog {
1359 reserved: 0x00,
1360 track_number_of_next_sector: 0x11,
1361 sector_number_of_next_sector: 0x01,
1362 reserved_2: &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
1363 file_entries: file_entries_1.to_vec(),
1364 catalog_by_filename: catalog_by_filename_1,
1365 };
1366 let catalog_2 = Catalog {
1367 reserved: 0x00,
1368 track_number_of_next_sector: 0x00,
1369 sector_number_of_next_sector: 0x00,
1370 reserved_2: &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
1371 file_entries: file_entries_2.to_vec(),
1372 catalog_by_filename: catalog_by_filename_2,
1373 };
1374
1375 let catalog_1_bytes = catalog_1.as_vec().unwrap();
1376 let catalog_2_bytes = catalog_2.as_vec().unwrap();
1377
1378 let mut tracks: Vec<Vec<&[u8]>> = Vec::new();
1379
1380 let mut disk_data: [[[u8; 256]; 16]; 35] = [[[0; 256]; 16]; 35];
1381
1382 for (i, byte) in catalog_1_bytes.iter().enumerate() {
1383 disk_data[17][2][i] = *byte;
1384 }
1385 for (i, byte) in catalog_2_bytes.iter().enumerate() {
1386 disk_data[17][1][i] = *byte;
1387 }
1388
1389 for track in &disk_data {
1390 let mut track_vec: Vec<&[u8]> = Vec::new();
1391 for sector in track {
1392 track_vec.push(sector);
1393 }
1394 tracks.push(track_vec);
1395 }
1396
1397 let catalog = parse_catalogs(&tracks, 17, 2).expect("Should be a valid FullCatalog");
1398 assert_eq!(catalog.file_entries.len(), 10);
1399 assert_eq!(
1400 catalog
1401 .file_entries
1402 .first()
1403 .expect("Should have at least one file")
1404 .filename()
1405 .expect("Should be a valid filename"),
1406 "A"
1407 );
1408 }
1409
1410 fn build_binary_test_file(size: u16) -> Vec<u8> {
1421 let mut data: Vec<u8> = Vec::new();
1422
1423 data.extend([0x00, 0x10]);
1425 data.extend(little_endian_word_to_bytes(size));
1427
1428 match size {
1429 0 => {}
1430 1 => {
1431 data.push(0x53);
1432 }
1433 2 => {
1434 data.extend([0x53, 0x44]);
1435 }
1436 3 => {
1437 data.extend([0x53, 0x54, 0x44]);
1438 }
1439 4 => {
1440 data.extend([0x53, 0x54, 0x4e, 0x44]);
1441 }
1442 5 => {
1443 data.extend([0x53, 0x54, 0x41, 0x4e, 0x44]);
1444 }
1445 6 => {
1446 data.extend([0x53, 0x54, 0x41, 0x45, 0x4e, 0x44]);
1447 }
1448 7 => {
1449 data.extend([0x53, 0x54, 0x41, 0x52, 0x45, 0x4e, 0x44]);
1450 }
1451 8 => {
1452 data.extend([0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x4e, 0x44]);
1453 }
1454 _ => {
1455 data.extend([0x53, 0x54, 0x41, 0x52, 0x54]);
1456 for i in 0..size - 8 {
1457 data.push((i % 0x100).try_into().unwrap());
1458 }
1459 data.extend([0x45, 0x4e, 0x44]);
1460 }
1461 }
1462
1463 data
1464 }
1465
1466 #[test]
1473 fn build_single_sector_binary_file_works() {
1474 let file_entry = FileEntry::new(0x0A, 0x0D, FileType::Binary, false, "BLAH", 0x0001);
1475 let file_entries_1 = [file_entry];
1476
1477 let mut catalog_by_filename_1: HashMap<String, FileEntry> = HashMap::new();
1478 file_entries_1.iter().for_each(|fe| {
1479 catalog_by_filename_1.insert(fe.filename().unwrap(), *fe);
1480 });
1481
1482 let catalog_1 = Catalog {
1483 reserved: 0x00,
1484 track_number_of_next_sector: 0x00,
1485 sector_number_of_next_sector: 0x00,
1486 reserved_2: &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
1487 file_entries: file_entries_1.to_vec(),
1488 catalog_by_filename: catalog_by_filename_1,
1489 };
1490
1491 let catalog_1_bytes = catalog_1.as_vec().unwrap();
1492
1493 let mut tracks: Vec<Vec<&[u8]>> = Vec::new();
1494
1495 let mut disk_data: [[[u8; 256]; 16]; 35] = [[[0; 256]; 16]; 35];
1496
1497 for (i, byte) in catalog_1_bytes.iter().enumerate() {
1498 disk_data[17][2][i] = *byte;
1499 }
1500
1501 let data = build_binary_test_file(200);
1503
1504 for (i, byte) in data.iter().enumerate() {
1505 disk_data[0x11][0x0B][i] = *byte;
1506 }
1507
1508 let tsp = TrackSectorPair {
1510 track_number: 0x11,
1511 sector_number: 0x0B,
1512 };
1513 let tsps: TrackSectorPairs = vec![tsp];
1514
1515 let tsl = TrackSectorList {
1516 reserved: 0,
1517 track_number_of_next_sector: None,
1518 sector_number_of_next_sector: None,
1519 reserved_2: &[0, 0],
1520 sector_offset_in_file: &[0, 0],
1521 reserved_3: &[0, 0, 0, 0, 0],
1522 track_sector_pairs: tsps,
1523 };
1524
1525 for (i, byte) in tsl.as_vec().unwrap().iter().enumerate() {
1526 disk_data[0x0A][0x0D][i] = *byte;
1527 }
1528
1529 for track in &disk_data {
1530 let mut track_vec: Vec<&[u8]> = Vec::new();
1531 for sector in track {
1532 track_vec.push(sector);
1533 }
1534 tracks.push(track_vec);
1535 }
1536
1537 let catalog = parse_catalogs(&tracks, 17, 2).expect("Should be a valid FullCatalog");
1539 assert_eq!(catalog.file_entries.len(), 1);
1540 assert_eq!(
1541 catalog
1542 .file_entries
1543 .first()
1544 .expect("Should have at least one file")
1545 .filename()
1546 .expect("Should be a valid filename"),
1547 "BLAH"
1548 );
1549
1550 let files = build_files(catalog.clone(), &tracks).unwrap();
1551 assert!(files.contains_key("BLAH"));
1552 assert!(!files.contains_key("BLARGH"));
1553
1554 let file = files.get("BLAH").unwrap();
1555
1556 assert_eq!(file.data.len(), 200);
1557 assert_eq!(&file.data[0..5], "START".as_bytes());
1558 for i in 0..192 {
1559 assert_eq!(file.data[(i as usize) + 5_usize], i);
1560 }
1561 assert_eq!(&file.data[197..200], "END".as_bytes());
1562 }
1563
1564 #[test]
1571 fn build_two_sector_binary_file_works() {
1572 let file_entry = FileEntry::new(0x0A, 0x0D, FileType::Binary, false, "BLAH", 0x0002);
1573 let file_entries_1 = [file_entry];
1574
1575 let mut catalog_by_filename_1: HashMap<String, FileEntry> = HashMap::new();
1576 file_entries_1.iter().for_each(|fe| {
1577 catalog_by_filename_1.insert(fe.filename().unwrap(), *fe);
1578 });
1579
1580 let catalog_1 = Catalog {
1581 reserved: 0x00,
1582 track_number_of_next_sector: 0x00,
1583 sector_number_of_next_sector: 0x00,
1584 reserved_2: &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
1585 file_entries: file_entries_1.to_vec(),
1586 catalog_by_filename: catalog_by_filename_1,
1587 };
1588
1589 let catalog_1_bytes = catalog_1.as_vec().unwrap();
1590
1591 let mut tracks: Vec<Vec<&[u8]>> = Vec::new();
1592
1593 let mut disk_data: [[[u8; 256]; 16]; 35] = [[[0; 256]; 16]; 35];
1594
1595 for (i, byte) in catalog_1_bytes.iter().enumerate() {
1596 disk_data[17][2][i] = *byte;
1597 }
1598
1599 let data = build_binary_test_file(400);
1602
1603 for (i, byte) in data[0..=255].iter().enumerate() {
1604 disk_data[0x11][0x0B][i] = *byte;
1605 }
1606
1607 for (i, byte) in data[256..].iter().enumerate() {
1608 disk_data[0x11][0x0C][i] = *byte;
1609 }
1610
1611 let tsp1 = TrackSectorPair {
1613 track_number: 0x11,
1614 sector_number: 0x0B,
1615 };
1616 let tsp2 = TrackSectorPair {
1617 track_number: 0x11,
1618 sector_number: 0x0C,
1619 };
1620 let tsps: TrackSectorPairs = vec![tsp1, tsp2];
1621
1622 let tsl = TrackSectorList {
1623 reserved: 0,
1624 track_number_of_next_sector: None,
1625 sector_number_of_next_sector: None,
1626 reserved_2: &[0, 0],
1627 sector_offset_in_file: &[0, 0],
1628 reserved_3: &[0, 0, 0, 0, 0],
1629 track_sector_pairs: tsps,
1630 };
1631
1632 for (i, byte) in tsl.as_vec().unwrap().iter().enumerate() {
1633 disk_data[0x0A][0x0D][i] = *byte;
1634 }
1635
1636 for track in &disk_data {
1637 let mut track_vec: Vec<&[u8]> = Vec::new();
1638 for sector in track {
1639 track_vec.push(sector);
1640 }
1641 tracks.push(track_vec);
1642 }
1643
1644 let catalog = parse_catalogs(&tracks, 17, 2).expect("Should be a valid FullCatalog");
1646 assert_eq!(catalog.file_entries.len(), 1);
1647 assert_eq!(
1648 catalog
1649 .file_entries
1650 .first()
1651 .expect("Should have at least one file")
1652 .filename()
1653 .expect("Should be a valid filename"),
1654 "BLAH"
1655 );
1656
1657 let files = build_files(catalog.clone(), &tracks).unwrap();
1658 assert!(files.contains_key("BLAH"));
1659 assert!(!files.contains_key("BLARGH"));
1660
1661 let file = files.get("BLAH").unwrap();
1662
1663 assert_eq!(file.data.len(), 400);
1664 assert_eq!(&file.data[0..5], "START".as_bytes());
1665 let expected_data: [u8; 392] = (0_u16..392_u16)
1666 .map(|i| (i % 0x100) as u8)
1667 .collect::<Vec<u8>>()
1668 .try_into()
1669 .unwrap();
1670
1671 assert_eq!(file.data[5..397], expected_data);
1672 assert_eq!(&file.data[397..400], "END".as_bytes());
1673 }
1674}