cpclib_disc/
edsk.rs

1// http://www.cpcwiki.eu/index.php/Format:DSK_disk_image_file_format
2
3use std::fs::File;
4use std::io::prelude::*;
5use std::string::ToString;
6
7use cpclib_common::bitflags::bitflags;
8use cpclib_common::camino::Utf8Path;
9use cpclib_common::itertools::{Itertools, zip};
10use delegate::delegate;
11use getset::Getters;
12
13use crate::disc::Disc;
14
15/// Computes the sector size as expected by the FDC from a human readable sector size
16#[allow(clippy::cast_possible_truncation)]
17#[allow(clippy::cast_sign_loss)]
18pub fn convert_real_sector_size_to_fdc_sector_size(mut size: u16) -> u8 {
19    let mut n = 0;
20    while size > 0x80 {
21        size >>= 1;
22        n += 1
23    }
24
25    n as _
26}
27
28/// Computes the sector size as expected by a human from the FDC
29pub fn convert_fdc_sector_size_to_real_sector_size(size: u8) -> u16 {
30    0x80 << size
31}
32
33#[derive(Debug, PartialEq, Copy, Clone, Ord, PartialOrd, Eq)]
34/// Symbolises the head of a disc.
35pub enum Head {
36    /// Side A of the disc for double sided discs
37    A,
38    /// Side B of the disc for double sided discs
39    B,
40    /// Side not specified for single sided discs. Should be deprecated in favor of A
41    Unspecified
42}
43
44impl From<Head> for i32 {
45    fn from(val: Head) -> Self {
46        match val {
47            Head::A => 0,
48            Head::B => 1,
49            Head::Unspecified => 0
50        }
51    }
52}
53
54#[allow(missing_docs)]
55impl From<u8> for Head {
56    fn from(val: u8) -> Self {
57        match val {
58            0 => Self::A,
59            1 => Self::B,
60            _ => Self::Unspecified
61        }
62    }
63}
64
65#[allow(missing_docs)]
66impl From<Head> for u8 {
67    fn from(val: Head) -> Self {
68        match val {
69            Head::A | Head::Unspecified => 0,
70            Head::B => 1
71        }
72    }
73}
74
75#[allow(missing_docs)]
76impl From<&Head> for u8 {
77    fn from(val: &Head) -> Self {
78        match val {
79            Head::A | &Head::Unspecified => 0,
80            Head::B => 1
81        }
82    }
83}
84
85/// Disc image files consist of a 0x100-byte disc info block and for each track a 0x100-byte track info block, followed by the data for every sector in that track. The new extended disk format is intended for some copy protected disks. Parts which are new in the extended format are marked with "*E*" (from our "Extended DISK Format Proposal, Rev.5").
86///
87///
88///
89///
90///
91///
92/// The Disc Information block
93/// Byte (Hex): 	Meaning:
94/// 00 - 21 	"MV - CPCEMU Disk-File\r\nDisk-Info\r\n"
95/// ("MV - CPC" is characteristic)
96///
97/// *E* -- "EXTENDED CPC DSK File\r\n\Disk-Info\r\n"
98/// ("EXTENDED" is characteristic)
99/// 22 - 2F 	unused (0)
100///
101/// *E* -- DSK creator (name of the utility)
102/// (no ending \0 needed!)
103/// 30 	number of tracks (40, 42, maybe 80)
104/// 31 	number of heads (1 or 2)
105/// 32 - 33 	size of one track (including 0x100-byte track info)
106/// With 9 sectors * 0x200 bytes + 0x100 byte track info = 0x1300.
107///
108/// *E* -- unused (0)
109/// 34 - FF 	unused (0)
110///
111/// *E* -- high bytes of track sizes for all tracks
112/// (computed in the same way as 32-33 for the normal format).
113///
114/// For single sided formats the table contains track sizes of just one side, otherwise for two alternating sides.
115/// A size of value 0 indicates an unformatted track.
116/// Actual track data length = table value * 256.
117/// Keep in mind that the image contains additional 256 bytes for each track info.
118#[derive(Getters, Debug, Default, PartialEq, Clone)]
119pub struct DiscInformation {
120    /// Specific to edsk
121    #[get = "pub"]
122    pub(crate) creator_name: String,
123    /// Number of tracks
124    #[get = "pub"]
125    pub(crate) number_of_tracks: u8,
126    /// Number of heads
127    #[get = "pub"]
128    pub(crate) number_of_heads: u8,
129    #[get = "pub"]
130    /// high bytes of track sizes for all tracks
131    pub(crate) track_size_table: Vec<u8> /* XXX for standard DSK only one value is provided It should be duplicated there */
132}
133
134#[allow(missing_docs)]
135impl DiscInformation {
136    fn creator_name_as_bytes(&self) -> [u8; 14] {
137        let mut data = [0; 14];
138        for (idx, byte) in self.creator_name.as_bytes().iter().take(14).enumerate() {
139            data[idx] = *byte;
140        }
141        data
142    }
143
144    /// Build an eDSK from a buffer of bytes
145    ///  TODO manage the case of standard dsk
146    pub fn from_buffer(buffer: &[u8]) -> Self {
147        assert_eq!(buffer.len(), 256);
148        assert_eq!(
149            String::from_utf8_lossy(&buffer[..34]).to_ascii_uppercase(),
150            "EXTENDED CPC DSK File\r\nDisk-Info\r\n".to_ascii_uppercase()
151        );
152
153        let creator_name = String::from_utf8_lossy(&buffer[0x22..=0x2F]);
154        let number_of_tracks = buffer[0x30];
155        let number_of_heads = buffer[0x31];
156        let track_size_table = &buffer[0x34..(0x34 + number_of_tracks * number_of_heads) as usize];
157
158        assert!(number_of_heads == 1 || number_of_heads == 2);
159
160        Self {
161            creator_name: creator_name.to_string(),
162            number_of_tracks,
163            number_of_heads,
164            track_size_table: track_size_table.to_vec()
165        }
166    }
167
168    fn to_buffer(&self, buffer: &mut Vec<u8>) {
169        buffer.extend_from_slice("EXTENDED CPC DSK File\r\nDisk-Info\r\n".as_bytes());
170        assert_eq!(buffer.len(), 34);
171
172        buffer.extend_from_slice(&self.creator_name_as_bytes());
173        assert_eq!(buffer.len(), 34 + 14);
174
175        buffer.push(self.number_of_tracks);
176        buffer.push(self.number_of_heads);
177        assert_eq!(buffer.len(), 34 + 14 + 1 + 1);
178
179        // XXX missing size of a track
180        buffer.push(0);
181        buffer.push(0);
182        assert_eq!(buffer.len(), 34 + 14 + 1 + 1 + 2);
183
184        buffer.extend_from_slice(&self.track_size_table);
185        assert_eq!(
186            buffer.len(),
187            34 + 14 + 1 + 1 + 2 + self.track_size_table.len()
188        );
189
190        assert!(buffer.len() <= 256);
191        // ensure we use 256 bytes
192        buffer.resize(256, 0x00);
193        assert_eq!(buffer.len(), 256);
194
195        // DEBUG mode XXX To remove
196        let from_buffer = Self::from_buffer(buffer);
197        assert_eq!(self, &from_buffer);
198    }
199
200    /// Check if the dsk is double sided
201    pub fn is_double_head(&self) -> bool {
202        self.number_of_heads == 2
203    }
204
205    /// Check if the dsk is single sided
206    pub fn is_single_head(&self) -> bool {
207        !self.is_double_head()
208    }
209
210    /// Returns the length of the track including the track information block
211    pub fn track_length(&self, track: u8, head: u8) -> u16 {
212        assert!(head < self.number_of_heads);
213
214        let track = track as usize;
215        let head = head as usize;
216        let idx = if self.is_single_head() {
217            track
218        }
219        else {
220            track * 2 + head
221        };
222
223        self.track_length_at_idx(idx)
224    }
225
226    /// Check if the wanted track is formatted
227    pub fn is_formatted(&self, track: u8, head: u8) -> bool {
228        self.track_length(track, head) > 0
229    }
230
231    /// Get the lenght of the required track
232    pub fn track_length_at_idx(&self, idx: usize) -> u16 {
233        256 * u16::from(self.track_size_table[idx])
234    }
235
236    /// Sum all the tracks lenght
237    pub fn total_tracks_lengths(&self) -> usize {
238        (0..self.number_of_distinct_tracks())
239            .map(|idx: usize| self.track_length_at_idx(idx) as usize)
240            .sum::<usize>()
241    }
242
243    /// Provide the number of different tracks
244    pub fn number_of_distinct_tracks(&self) -> usize {
245        self.track_size_table.len()
246    }
247}
248
249/// Byte (Hex) 	Meaning:
250/// 00 - 0C 	Track-Info\r\n
251/// 0D - 0F 	unused (0)
252/// 10 	track number (0 to number of tracks-1)
253/// 11 	head number (0 or 1)
254/// 12 - 13 	unused (0)
255/// Format track parameters:
256/// 14 	BPS (bytes per sector) (2 for 0x200 bytes)
257/// 15 	SPT (sectors per track) (9, at the most 18)
258/// 16 	GAP#3 format (gap for formatting; 0x4E)
259/// 17 	Filling byte (filling byte for formatting; 0xE5)
260/// Sector info (for every sector at a time):
261/// 18+i 	track number (sector ID information)
262/// 19+i 	head number (sector ID information)
263/// 1A+i 	sector number (sector ID information)
264/// 1B+i 	BPS (sector ID information)
265/// 1C+i 	state 1 error code (0)
266/// 1D+i 	state 2 error code (0)
267/// 1E+i,1F+i 	unused (0)
268///
269/// *E* -- sector data length in bytes (little endian notation).
270/// This allows different sector sizes in a track.
271/// It is computed as (0x0080 << real_BPS).
272///
273/// Annotations:
274///
275///   - The sector data must follow the track information block in the order of the sector IDs. No track or sector may be omitted.
276///   - With double sided formats, the tracks are alternating, e.g. track 0 head 0, track 0 head 1, track 1 ...
277///   - Use CPCTRANS to copy CPC discs into this format.
278
279#[allow(missing_docs)]
280#[derive(Getters, Debug, Default, PartialEq, Clone)]
281pub struct TrackInformation {
282    /// track number (0 to number of tracks-1)
283    #[get = "pub"]
284    pub(crate) track_number: u8,
285    /// head number (0 or 1)
286    #[get = "pub"]
287    pub(crate) head_number: u8,
288    #[get = "pub"]
289    /// BPS (bytes per sector) (2 for 0x200 bytes)
290    pub(crate) sector_size: u8, // XXX check if really needed to be stored
291    /// SPT (sectors per track) (9, at the most 18)
292    #[get = "pub"]
293    pub(crate) number_of_sectors: u8,
294    /// GAP#3 format (gap for formatting; 0x4E)
295    #[get = "pub"]
296    pub(crate) gap3_length: u8,
297    /// Filling byte (filling byte for formatting; 0xE5)
298    #[get = "pub"]
299    pub(crate) filler_byte: u8,
300    /// Returns the data rate
301    #[get = "pub"]
302    pub(crate) data_rate: DataRate,
303    /// Returns the recordingmode
304    #[get = "pub"]
305    pub(crate) recording_mode: RecordingMode,
306    /// List of sectors
307    #[get = "pub"]
308    pub(crate) sector_information_list: SectorInformationList,
309    /// The size taken by the track + header in the dsk. This is a duplicated information obtained in the DiscInformation bloc
310    #[get = "pub"]
311    pub(crate) track_size: u16
312}
313
314#[allow(missing_docs)]
315impl TrackInformation {
316    /// TODO find a nicer (with Either ?) way to manipulate unformatted tracks
317    pub fn unformatted() -> Self {
318        Self::default()
319    }
320
321    /// Returns the real size of the track (i.e. after removing the header)
322    pub fn real_track_size(&self) -> u16 {
323        self.track_size() - 256
324    }
325}
326
327#[allow(missing_docs)]
328impl TrackInformation {
329    delegate! {
330        to self.sector_information_list {
331            pub fn sector(&self, sector_id: u8) -> Option<&Sector>;
332            pub fn sector_mut(&mut self, sector_id: u8) -> Option<&mut Sector>;
333        }
334    }
335
336    #[deprecated(
337        note = "Note sure it should be used as each sector store this information and different sizes are possible"
338    )]
339    pub fn sector_size_human_readable(&self) -> u16 {
340        convert_fdc_sector_size_to_real_sector_size(self.sector_size)
341    }
342
343    /// Returns the ID of the sector following this one
344    pub fn next_sector_id(&self, sector: u8) -> Option<u8> {
345        for idx in 0..(self.number_of_sectors() - 1) {
346            let current_sector = &self.sector_information_list.sectors[idx as usize];
347            let next_sector = &self.sector_information_list.sectors[idx as usize + 1];
348
349            if *current_sector.sector_id() == sector {
350                return Some(*next_sector.sector_id());
351            }
352        }
353
354        None
355    }
356
357    /// Fail if the track has no sector
358    pub fn min_sector(&self) -> u8 {
359        self.sector_information_list
360            .sectors()
361            .iter()
362            .map(|s| s.sector_information_bloc.sector_id)
363            .min()
364            .unwrap()
365    }
366
367    /// Compute the sum of data contained by all the sectors.
368    /// Only serves for debug purposes
369    pub fn data_sum(&self) -> usize {
370        self.sector_information_list
371            .sectors
372            .iter()
373            .map(Sector::data_sum)
374            .sum::<usize>()
375    }
376
377    pub fn corresponds_to(&self, track: u8, head: u8) -> bool {
378        self.track_number == track && self.head_number == head
379    }
380
381    #[allow(clippy::cast_possible_truncation)]
382    pub fn from_buffer(buffer: &[u8]) -> Self {
383        if String::from_utf8_lossy(&buffer[..0xC]).to_ascii_uppercase()
384            != "Track-info\r\n".to_ascii_uppercase()
385        {
386            panic!(
387                "Track buffer does not seem coherent\n{:?}...",
388                &buffer[..0xC]
389            );
390        }
391
392        let track_size = buffer.len() as u16;
393        let track_number = buffer[0x10];
394        let head_number = buffer[0x11];
395        let sector_size = buffer[0x14];
396        let number_of_sectors = buffer[0x15];
397        let gap3_length = buffer[0x16];
398        let filler_byte = buffer[0x17];
399        let data_rate: DataRate = buffer[0x12].into();
400        let recording_mode = buffer[0x13].into();
401
402        println!(
403            "Track {track_number} Head {head_number} sector_size {sector_size} nb_sectors {number_of_sectors} gap length {gap3_length:x}, filler_byte {filler_byte:x}"
404        );
405        let sector_information_list =
406            SectorInformationList::from_buffer(&buffer[0x18..], number_of_sectors);
407
408        let track_info = Self {
409            track_number,
410            head_number,
411            sector_size,
412            number_of_sectors,
413            gap3_length,
414            filler_byte,
415            data_rate,
416            recording_mode,
417            sector_information_list,
418            track_size
419        };
420
421        assert!(track_info.track_size != 0);
422
423        assert_eq!(
424            track_info.real_track_size(),
425            track_info.compute_track_size() as u16,
426            "Wrong track_info {track_info:?}"
427        );
428        track_info
429    }
430
431    /// http://www.cpcwiki.eu/index.php/Format:DSK_disk_image_file_format#TRACK_INFORMATION_BLOCK_2
432    ///
433    /// offset 	description 	bytes
434    /// 00 - 0b 	"Track-Info\r\n" 	12
435    /// 0c - 0f 	unused 	4
436    /// 10 	track number 	1
437    /// 11 	Head number 	1
438    /// 12 - 13 	unused 	2
439    /// 14 	sector size 	1
440    /// 15 	number of sectors 	1
441    /// 16 	GAP#3 length 	1
442    /// 17 	filler byte 	1
443    /// 18 - xx 	Sector Information List 	xx
444    ///
445    /// Extensions
446    /// offset 	description 	bytes
447    /// 12 	Data rate. (See note 1 and note 3) 	1
448    /// 13 	Recording mode. (See note 2 and note 3) 	1
449    pub fn to_buffer(&self, buffer: &mut Vec<u8>) {
450        let start_size = buffer.len();
451
452        // low byte MUST be null
453        assert_eq!(start_size % 256, 0);
454
455        // 00 - 0b 	"Track-Info\r\n" 	12
456        buffer.extend_from_slice(&"Track-Info\r\n".as_bytes()[..12]);
457        assert_eq!(buffer.len() - start_size, 12);
458
459        // 0c - 0f 	unused 	4
460        buffer.push(0);
461        buffer.push(0);
462        buffer.push(0);
463        buffer.push(0);
464
465        // 10 	track number 	1
466        buffer.push(self.track_number);
467
468        // 11 	Head number 	1
469        buffer.push(self.head_number);
470
471        // 12 	Data rate. (See note 1 and note 3) 	1
472        buffer.push(self.data_rate.into());
473
474        // 13 	Recording mode. (See note 2 and note 3) 	1
475        buffer.push(self.recording_mode.into());
476
477        // 14 	sector size 	1
478        buffer.push(self.sector_size);
479
480        // 15 	number of sectors 	1
481        buffer.push(self.number_of_sectors);
482
483        // 16 	GAP#3 length 	1
484        buffer.push(self.gap3_length);
485
486        // 17 	filler byte 	1
487        buffer.push(self.filler_byte);
488
489        assert_eq!(buffer.len() - start_size, 0x18);
490
491        // 18 - xx 	Sector Information List 	x
492        // Inject sectors information list
493        self.sector_information_list.sectors.iter().for_each(|s| {
494            s.sector_information_bloc.to_buffer(buffer);
495        });
496
497        // Ensure next position has a low byte value of 0
498        let added_bytes = buffer.len() - start_size;
499        let missing_bytes = 256 - added_bytes;
500        buffer.resize(buffer.len() + missing_bytes, 0);
501        assert_eq!(buffer.len() % 256, 0);
502
503        // Inject sectors information data
504        self.sector_information_list.sectors.iter().for_each(|s| {
505            buffer.extend_from_slice(&s.values);
506        });
507
508        // TODO find why this coded was previously present as it raise issues
509        // Ensure the size is correct
510        // let added_bytes = (buffer.len() - start_size) as u16;
511        // assert!(
512        // added_bytes <= self.track_size,
513        // format!("{} != {}", added_bytes, self.track_size)
514        // );
515        // let missing_bytes = self.track_size - added_bytes;
516        // if missing_bytes != 0 {
517        // buffer.resize(buffer.len() + missing_bytes as usize, 0);
518        // }
519        // Add padding
520        assert!(buffer.len().is_multiple_of(256));
521    }
522
523    /// TODO remove this method or set it private
524    pub fn total_size(&self) -> usize {
525        self.sector_information_list
526            .sectors
527            .iter()
528            .map(|info| info.sector_information_bloc.data_length as usize)
529            .sum()
530    }
531
532    /// Track size has it should be written in the DSK
533    pub fn compute_track_size(&self) -> usize {
534        let size = self.total_size();
535        if size.is_multiple_of(256) {
536            size
537        }
538        else {
539            let mut s = size;
540            // TODO implement an efficient version
541            while !s.is_multiple_of(256) {
542                s += 1;
543            }
544            s
545        }
546    }
547}
548
549#[derive(Debug, Copy, Clone, PartialEq)]
550#[allow(missing_docs)]
551pub enum DataRate {
552    Unknown = 0,
553    SingleOrDoubleDensity = 1,
554    HighDensity = 2,
555    ExtendedDensity = 3
556}
557
558#[allow(missing_docs)]
559impl Default for DataRate {
560    fn default() -> Self {
561        Self::Unknown
562    }
563}
564
565#[allow(missing_docs)]
566impl From<u8> for DataRate {
567    fn from(b: u8) -> Self {
568        match b {
569            0 => Self::Unknown,
570            1 => Self::SingleOrDoubleDensity,
571            2 => Self::HighDensity,
572            3 => Self::ExtendedDensity,
573            _ => unreachable!()
574        }
575    }
576}
577
578#[allow(missing_docs)]
579impl From<DataRate> for u8 {
580    fn from(val: DataRate) -> Self {
581        match val {
582            DataRate::Unknown => 0,
583            DataRate::SingleOrDoubleDensity => 1,
584            DataRate::HighDensity => 2,
585            DataRate::ExtendedDensity => 3
586        }
587    }
588}
589
590#[derive(Debug, Clone, Copy, PartialEq)]
591#[allow(missing_docs)]
592pub enum RecordingMode {
593    Unknown = 0,
594    FM = 1,
595    MFM = 2
596}
597
598#[allow(missing_docs)]
599impl Default for RecordingMode {
600    fn default() -> Self {
601        Self::Unknown
602    }
603}
604
605#[allow(missing_docs)]
606impl From<u8> for RecordingMode {
607    fn from(b: u8) -> Self {
608        match b {
609            0 => Self::Unknown,
610            1 => Self::FM,
611            2 => Self::MFM,
612            _ => unreachable!()
613        }
614    }
615}
616
617#[allow(missing_docs)]
618impl From<RecordingMode> for u8 {
619    fn from(val: RecordingMode) -> Self {
620        match val {
621            RecordingMode::Unknown => 0,
622            RecordingMode::FM => 1,
623            RecordingMode::MFM => 2
624        }
625    }
626}
627
628#[derive(Getters, Debug, Default, PartialEq, Clone, Copy)]
629#[allow(missing_docs)]
630pub struct SectorInformation {
631    /// track (equivalent to C parameter in NEC765 commands)
632    #[get = "pub"]
633    pub(crate) track: u8,
634    /// Head (equivalent to H parameter in NEC765 commands)
635    #[get = "pub"]
636    pub(crate) head: u8,
637    /// sector ID (equivalent to R parameter in NEC765 commands)
638    #[get = "pub"]
639    pub(crate) sector_id: u8,
640    /// sector size (equivalent to N parameter in NEC765 commands)
641    #[get = "pub"]
642    pub(crate) sector_size: u8,
643    /// FDC status register 1 (equivalent to NEC765 ST1 status register)
644    #[get = "pub"]
645    pub(crate) fdc_status_register_1: u8,
646    /// FDC status register 2 (equivalent to NEC765 ST2 status register)
647    #[get = "pub"]
648    pub(crate) fdc_status_register_2: u8,
649    /// actual data length in bytes
650    #[get = "pub"]
651    pub(crate) data_length: u16 // in bytes, little endian notation
652}
653
654#[allow(missing_docs)]
655#[allow(clippy::trivially_copy_pass_by_ref)]
656impl SectorInformation {
657    /// Return the real size of the sector
658    pub fn len(&self) -> usize {
659        self.sector_size as usize * 256
660    }
661
662    /// Check if the sector is empty
663    pub fn is_empty(&self) -> bool {
664        self.len() == 0
665    }
666
667    pub fn from_buffer(buffer: &[u8]) -> Self {
668        Self {
669            track: buffer[0x00],
670            head: buffer[0x01],
671            sector_id: buffer[0x02],
672            sector_size: buffer[0x03],
673            fdc_status_register_1: buffer[0x04],
674            fdc_status_register_2: buffer[0x05],
675            data_length: u16::from(buffer[0x06]) + (u16::from(buffer[0x07]) * 256)
676        }
677    }
678
679    /// 00 	track (equivalent to C parameter in NEC765 commands) 	1
680    /// 01 	Head (equivalent to H parameter in NEC765 commands) 	1
681    /// 02 	sector ID (equivalent to R parameter in NEC765 commands) 	1
682    /// 03 	sector size (equivalent to N parameter in NEC765 commands) 	1
683    /// 04 	FDC status register 1 (equivalent to NEC765 ST1 status register) 	1
684    /// 05 	FDC status register 2 (equivalent to NEC765 ST2 status register) 	1
685    /// 06 - 07 	actual data length in bytes 	2
686    #[allow(clippy::cast_possible_truncation)]
687    pub fn to_buffer(&self, buffer: &mut Vec<u8>) {
688        buffer.push(self.track);
689        buffer.push(self.head);
690        buffer.push(self.sector_id);
691        buffer.push(self.sector_size);
692        buffer.push(self.fdc_status_register_1);
693        buffer.push(self.fdc_status_register_2);
694
695        // Specific for extended image
696        buffer.push((self.data_length % 256) as u8);
697        buffer.push((self.data_length / 256) as u8);
698    }
699}
700
701#[derive(Debug, Default, PartialEq, Clone)]
702#[allow(missing_docs)]
703pub struct SectorInformationList {
704    // sectors: Vec<Sector>
705    pub(crate) sectors: Vec<Sector>
706}
707
708#[allow(missing_docs)]
709impl SectorInformationList {
710    pub fn sectors(&self) -> &[Sector] {
711        &self.sectors
712    }
713
714    /// Return the number of sectors
715    pub fn len(&self) -> usize {
716        self.sectors.len()
717    }
718
719    pub fn is_empty(&self) -> bool {
720        self.len() == 0
721    }
722
723    /// Add a sector
724    pub fn add_sector(&mut self, sector: Sector) {
725        self.sectors.push(sector);
726    }
727
728    pub fn from_buffer(buffer: &[u8], number_of_sectors: u8) -> Self {
729        let mut list_info = Vec::new();
730        let mut list_data = Vec::new();
731        let mut consummed_bytes = 0;
732
733        // Get the information
734        for _sector_number in 0..number_of_sectors {
735            let current_buffer = &buffer[consummed_bytes..];
736            let sector = SectorInformation::from_buffer(current_buffer);
737            consummed_bytes += 8;
738            list_info.push(sector);
739        }
740
741        // Get the data
742        consummed_bytes = 256 - 0x18; // Skip the unused bytes
743        for sector in &list_info {
744            let current_sector_size = sector.data_length as usize;
745            let current_buffer = &buffer[consummed_bytes..consummed_bytes + current_sector_size];
746            let sector_bytes = current_buffer.to_vec();
747            assert_eq!(sector_bytes.len(), current_sector_size);
748            list_data.push(sector_bytes);
749            consummed_bytes += current_sector_size;
750        }
751
752        // merge them
753        let info_drain = list_info.drain(..);
754        let data_drain = list_data.drain(..);
755        let sectors = zip(info_drain, data_drain)
756            .map(|(info, data)| {
757                assert_eq!(info.data_length as usize, data.len());
758                Sector {
759                    sector_information_bloc: info,
760                    values: data
761                }
762            })
763            .collect::<Vec<Sector>>();
764
765        Self { sectors }
766    }
767
768    pub fn sector(&self, sector_id: u8) -> Option<&Sector> {
769        self.sectors
770            .iter()
771            .find(|sector| sector.sector_information_bloc.sector_id == sector_id)
772    }
773
774    /// Returns the sector that correspond to the requested id
775    pub fn sector_mut(&mut self, sector_id: u8) -> Option<&mut Sector> {
776        self.sectors
777            .iter_mut()
778            .find(|sector| sector.sector_information_bloc.sector_id == sector_id)
779    }
780
781    /// Fill the information list with sectors corresponding to the provided arguments
782    #[allow(clippy::cast_possible_truncation)]
783    pub fn fill_with(
784        &mut self,
785        ids: &[u8],
786        heads: &[u8],
787        track_number: u8,
788        sector_size: u8,
789        filler_byte: u8
790    ) {
791        assert_eq!(ids.len(), heads.len());
792        assert_eq!(self.len(), 0);
793
794        for idx in 0..ids.len() {
795            let mut sector = Sector::default();
796
797            sector.sector_information_bloc.track = track_number;
798            sector.sector_information_bloc.sector_size = sector_size;
799            sector.sector_information_bloc.sector_id = ids[idx];
800            sector.sector_information_bloc.head = heads[idx];
801
802            let data_size = convert_fdc_sector_size_to_real_sector_size(
803                sector.sector_information_bloc.sector_size
804            ) as usize;
805            sector.values.resize(data_size, filler_byte);
806            sector.values.fill(filler_byte);
807            sector.sector_information_bloc.data_length = sector.values.len() as u16;
808
809            self.add_sector(sector);
810        }
811    }
812}
813
814bitflags! {
815    struct FdcStatusRegister1: u8 {
816        const END_OF_CYLINDER = 1<<7;
817        const DATA_ERROR = 1<<5;
818        const NO_DATA = 1<<2;
819        const MISSING_ADDRESS_MARK = 1<<0;
820    }
821}
822
823bitflags! {
824    struct FdcStatusRegister2: u8 {
825        const CONTROL_MARK = 1<<5;
826        const DATA_ERROR_IN_DATA_FIELD = 1<<5;
827        const MISSING_ADDRESS_MARK_IN_DATA_FIELD = 1<<0;
828    }
829}
830
831#[derive(Getters, Debug, Default, PartialEq, Clone)]
832#[allow(missing_docs)]
833#[allow(unused)]
834pub struct Sector {
835    #[getset(get)]
836    pub(crate) sector_information_bloc: SectorInformation,
837    /// Some DSK seem to have a vector with not the right size. In tFor this reason, it is better to not give access to it directly
838    pub(crate) values: Vec<u8>
839}
840
841#[allow(missing_docs)]
842impl Sector {
843    delegate! {
844        to self.sector_information_bloc {
845            pub fn sector_id(&self) -> &u8;
846        }
847    }
848
849    /// Number of bytes stored in the sector
850    #[allow(clippy::cast_possible_truncation)]
851    pub fn real_size(&self) -> u16 {
852        self.values.len() as u16
853    }
854
855    pub fn len(&self) -> u16 {
856        self.sector_information_bloc.len() as u16
857    }
858
859    pub fn is_empty(&self) -> bool {
860        self.len() == 0
861    }
862
863    pub fn data_sum(&self) -> usize {
864        self.values().iter().map(|&v| v as usize).sum::<usize>()
865    }
866
867    pub fn values(&self) -> &[u8] {
868        &self.values[..self.len() as usize]
869    }
870
871    pub fn values_mut(&mut self) -> &mut [u8] {
872        let idx = self.len() as usize;
873        &mut self.values[..idx]
874    }
875
876    /// Set all the values stored in the sector
877    pub fn set_values(&mut self, data: &[u8]) -> Result<(), String> {
878        if data.len() < self.len() as usize {
879            return Err(format!(
880                "You cannot insert {} bytes in a sector of size {}.",
881                data.len(),
882                self.len()
883            ));
884        }
885
886        if data.len() > self.len() as usize {
887            return Err(format!(
888                "Smaller data of {} bytes to put in a sector of size {}.",
889                data.len(),
890                self.len()
891            ));
892        }
893
894        self.values[..].clone_from_slice(data);
895        Ok(())
896    }
897}
898
899#[derive(Default, PartialEq, Debug, Clone)]
900#[allow(missing_docs)]
901pub struct TrackInformationList {
902    pub(crate) list: Vec<TrackInformation>
903}
904
905#[allow(missing_docs)]
906impl TrackInformationList {
907    fn from_buffer_and_disc_information(buffer: &[u8], disc_info: &DiscInformation) -> Self {
908        let mut consummed_bytes: usize = 0;
909        let mut list = Vec::new();
910
911        for track_number in 0..disc_info.number_of_tracks {
912            for head_nb in 0..disc_info.number_of_heads {
913                // Size of the track data + header
914                let current_track_size = disc_info.track_length(track_number, head_nb) as usize;
915                let track_buffer = &buffer[consummed_bytes..(consummed_bytes + current_track_size)];
916                if current_track_size > 0 {
917                    list.push(TrackInformation::from_buffer(track_buffer));
918                }
919                else {
920                    eprintln!("Track {track_number} is unformatted");
921                    list.push(TrackInformation::unformatted());
922                }
923                consummed_bytes += current_track_size;
924            }
925        }
926
927        Self { list }
928    }
929
930    /// Write the track list in the given buffer
931    fn to_buffer(&self, buffer: &mut Vec<u8>) {
932        for track in &self.list {
933            track.to_buffer(buffer);
934        }
935    }
936
937    /// Add an empty track and return it. It is up to the caller to properly feed it
938    pub fn add_empty_track(&mut self) -> &mut TrackInformation {
939        let track = TrackInformation::default();
940        self.list.push(track);
941        self.list.last_mut().unwrap()
942    }
943
944    /// Returns the tracks for the given head
945    pub fn tracks_for_head(&self, head: Head) -> impl Iterator<Item = &TrackInformation> {
946        let head: u8 = head.into();
947        self.list
948            .iter()
949            .filter(move |info| info.head_number == head)
950    }
951
952    /// Returns the track following this one
953    pub fn next_track(&self, track: &TrackInformation) -> Option<&TrackInformation> {
954        for idx in 0..(self.list.len() - 1) {
955            let current_track = &self.list[idx];
956            let next_track = &self.list[idx + 1];
957
958            if current_track == track {
959                return Some(next_track);
960            }
961        }
962
963        None
964    }
965}
966
967#[derive(PartialEq, Debug, Clone)]
968#[allow(missing_docs)]
969pub struct ExtendedDsk {
970    pub(crate) disc_information_bloc: DiscInformation,
971    pub(crate) track_list: TrackInformationList
972}
973
974impl Default for ExtendedDsk {
975    fn default() -> Self {
976        let cfg = crate::cfg::DiscConfig::single_head_data42_format();
977
978        crate::builder::build_edsk_from_cfg(&cfg)
979    }
980}
981
982#[allow(missing_docs)]
983impl ExtendedDsk {
984    pub fn from_buffer(buffer: &[u8]) -> Self {
985        assert!(buffer.len() >= 256);
986        let disc_info = DiscInformation::from_buffer(&buffer[..256]);
987
988        println!(
989            "Disc info {:?} / total {} / nb_tracks {}",
990            disc_info,
991            disc_info.total_tracks_lengths(),
992            disc_info.number_of_distinct_tracks()
993        );
994        let track_list =
995            TrackInformationList::from_buffer_and_disc_information(&buffer[256..], &disc_info);
996
997        Self {
998            disc_information_bloc: disc_info,
999            track_list
1000        }
1001    }
1002
1003    /// Add the file in consecutive sectors
1004    pub fn add_file_sequentially(
1005        &mut self,
1006        head: u8,
1007        track: u8,
1008        sector: u8,
1009        buffer: &[u8]
1010    ) -> Result<(u8, u8, u8), String> {
1011        let mut pos = (head, track, sector);
1012        let mut consummed = 0;
1013        while consummed < buffer.len() {
1014            let current_sector = self
1015                .sector_mut(pos.0, pos.1, pos.2)
1016                .ok_or_else(|| "Sector not found".to_owned())?;
1017
1018            let sector_size = current_sector.len() as usize;
1019            let current_data = &buffer[consummed..consummed + sector_size];
1020            current_sector.set_values(current_data)?;
1021            consummed += sector_size;
1022
1023            let next_pos = self
1024                .next_position(pos.0, pos.1, pos.2)
1025                .ok_or_else(|| "No more position available".to_owned())?;
1026            pos = next_pos;
1027        }
1028
1029        Ok(pos)
1030    }
1031
1032    /// Compute the next sector position if possible
1033    /// XXX check if Head should be the logic or physical one
1034    /// XXX the two behaviors are mixed there ...
1035    fn next_position(&self, head: u8, track: u8, sector: u8) -> Option<(u8, u8, u8)> {
1036        // Retrieve the current track and exit if does not exist
1037        let current_track = self.get_track_information(
1038            head, // Physical
1039            track
1040        )?;
1041
1042        // Return the next sector if exist
1043        if let Some(next_sector) = current_track.next_sector_id(sector) {
1044            return Some((head, track, next_sector));
1045        }
1046
1047        // Search the next track
1048        let next_track = self.track_list.next_track(current_track)?;
1049
1050        Some((
1051            *next_track.head_number(), // XXX  logical
1052            *next_track.track_number(),
1053            next_track.min_sector()
1054        ))
1055    }
1056
1057    /// Write the dsk in the provided buffer
1058    pub fn to_buffer(&self, buffer: &mut Vec<u8>) {
1059        self.disc_information_bloc.to_buffer(buffer);
1060        self.track_list.to_buffer(buffer);
1061    }
1062
1063    pub fn is_double_head(&self) -> bool {
1064        self.disc_information_bloc.is_double_head()
1065    }
1066
1067    #[deprecated]
1068    pub fn nb_tracks_per_side(&self) -> u8 {
1069        self.nb_tracks_per_head()
1070    }
1071
1072    pub fn nb_heads(&self) -> u8 {
1073        self.disc_information_bloc.number_of_heads
1074    }
1075
1076    pub fn get_track_information<S: Into<Head>>(
1077        &self,
1078        head: S,
1079        track: u8
1080    ) -> Option<&TrackInformation> {
1081        let idx = self.get_track_idx(head.into(), track);
1082        self.track_list.list.get(idx)
1083    }
1084
1085    pub fn get_track_information_mut<S: Into<Head>>(
1086        &mut self,
1087        head: S,
1088        track: u8
1089    ) -> Option<&mut TrackInformation> {
1090        let idx = self.get_track_idx(head.into(), track);
1091        self.track_list.list.get_mut(idx)
1092    }
1093
1094    /// Search and returns the appropriate sector
1095    pub fn sector<S: Into<Head>>(&self, head: S, track: u8, sector_id: u8) -> Option<&Sector> {
1096        self.get_track_information(head.into(), track)
1097            .and_then(|track| track.sector(sector_id))
1098    }
1099
1100    /// Search and returns the appropriate mutable sector
1101    pub fn sector_mut<S: Into<Head>>(
1102        &mut self,
1103        head: S,
1104        track: u8,
1105        sector_id: u8
1106    ) -> Option<&mut Sector> {
1107        self.get_track_information_mut(head.into(), track)
1108            .and_then(|track| track.sector_mut(sector_id))
1109    }
1110
1111    fn get_track_idx(&self, head: Head, track: u8) -> usize {
1112        if self.disc_information_bloc.is_double_head() {
1113            let head = match head {
1114                Head::A => 0,
1115                Head::B => 1,
1116                Head::Unspecified => panic!("You must specify a Head for a double Headed disc.")
1117            };
1118            track as usize * 2 + head
1119        }
1120        else {
1121            if let Head::B = head {
1122                panic!("You cannot select Head B in a single Head disc");
1123            }
1124            track as usize
1125        }
1126    }
1127
1128    /// Return all the bytes of the given track
1129    pub fn track_bytes<H: Into<Head>>(&self, head: H, track: u8) -> Option<Vec<u8>> {
1130        match self.get_track_information(head, track) {
1131            Some(track) => {
1132                let mut bytes = Vec::new();
1133                for sector in track.sector_information_list.sectors() {
1134                    bytes.extend(sector.values().iter());
1135                }
1136                Some(bytes)
1137            },
1138            _ => None
1139        }
1140    }
1141
1142    /// Compute the datasum for the given track
1143    pub fn data_sum(&self, head: Head) -> usize {
1144        self.track_list
1145            .tracks_for_head(head)
1146            .map(TrackInformation::data_sum)
1147            .sum()
1148    }
1149
1150    /// Returns all the tracks
1151    pub fn tracks(&self) -> &[TrackInformation] {
1152        &self.track_list.list
1153    }
1154
1155    /// Returns the number of tracks
1156    pub fn nb_tracks(&self) -> usize {
1157        self.tracks().len()
1158    }
1159}
1160
1161impl Disc for ExtendedDsk {
1162    /// open an extended dsk from an existing file
1163    fn open<P>(path: P) -> Result<Self, String>
1164    where P: AsRef<Utf8Path> {
1165        let path = path.as_ref();
1166        // Read the whole file
1167        let buffer = {
1168            let mut f = File::open(path).map_err(|e| e.to_string())?;
1169            let mut buffer = Vec::new();
1170            f.read_to_end(&mut buffer).map_err(|e| e.to_string())?;
1171            buffer
1172        };
1173
1174        Ok(Self::from_buffer(&buffer))
1175    }
1176
1177    /// Save the dsk in a file one disc
1178    fn save<P>(&self, path: P) -> Result<(), String>
1179    where P: AsRef<Utf8Path> {
1180        let path = path.as_ref();
1181        let mut file_buffer = File::create(path).map_err(|e| e.to_string())?;
1182        let mut memory_buffer = Vec::new();
1183        self.to_buffer(&mut memory_buffer);
1184        file_buffer
1185            .write_all(&memory_buffer)
1186            .map_err(|e| e.to_string())
1187    }
1188
1189    /// Return the smallest sector id over all tracks
1190    fn global_min_sector<S: Into<Head>>(&self, _side: S) -> u8 {
1191        self.tracks()
1192            .iter()
1193            .map(TrackInformation::min_sector)
1194            .min()
1195            .unwrap()
1196    }
1197
1198    fn sector_read_bytes<S: Into<Head>>(
1199        &self,
1200        head: S,
1201        track: u8,
1202        sector_id: u8
1203    ) -> Option<Vec<u8>> {
1204        self.sector(head, track, sector_id)
1205            .map(|s| s.values.clone())
1206    }
1207
1208    fn sector_write_bytes<S: Into<Head>>(
1209        &mut self,
1210        head: S,
1211        track: u8,
1212        sector_id: u8,
1213        bytes: &[u8]
1214    ) -> Result<(), String> {
1215        let head = head.into();
1216        let sector = self.sector_mut(head, track, sector_id).ok_or_else(|| {
1217            format!(
1218                "Head {head:?} track {track} sector 0x{sector_id:X} missing",
1219            )
1220        }).unwrap()/*?*/;
1221        sector.set_values(bytes)?;
1222
1223        Ok(())
1224    }
1225
1226    fn track_min_sector<S: Into<Head>>(&self, side: S, track: u8) -> u8 {
1227        self.get_track_information(side, track)
1228            .unwrap()
1229            .min_sector()
1230    }
1231
1232    // We assume we have the same number of tracks per Head.
1233    // Need to be modified the day ot will not be the case.
1234    #[allow(clippy::cast_possible_truncation)]
1235    fn nb_tracks_per_head(&self) -> u8 {
1236        let val = if self.disc_information_bloc.is_single_head() {
1237            self.track_list.list.len()
1238        }
1239        else {
1240            self.track_list.list.len() / 2
1241        };
1242        (val & 0xFF) as u8
1243    }
1244}