1use std::fs::File;
8use std::io::{Read, Seek, SeekFrom, Write};
9use thiserror::Error;
10
11#[cfg(test)]
12mod tests;
13
14const D64_35_TRACKS_SIZE: usize = 174848;
15const D64_40_TRACKS_SIZE: usize = 196608;
16const MAX_TRACKS: u8 = 40;
17const SECTORS_PER_TRACK: [u8; 40] = [
18 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 19, 19, 19, 19, 19, 19, 19,
19 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
20];
21
22#[derive(Error, Debug)]
23pub enum D64Error {
24 #[error("IO error: {0}")]
25 Io(#[from] std::io::Error),
26 #[error("Invalid D64 file size")]
27 InvalidFileSize,
28 #[error("Invalid track or sector")]
29 InvalidTrackSector,
30 #[error("File not found")]
31 FileNotFound,
32 #[error("Disk full")]
33 DiskFull,
34}
35
36pub struct D64 {
37 pub data: Vec<u8>,
38 pub tracks: u8,
39}
40
41pub struct BAM {
42 pub tracks: u8,
43 pub free_sectors: [u8; 40],
44 pub bitmap: [[u8; 3]; 40],
45 pub disk_name: [u8; 16],
46 pub disk_id: [u8; 2],
47 pub dos_type: u8,
48}
49
50pub fn petscii_to_ascii(petscii: &[u8]) -> String {
51 petscii
52 .iter()
53 .map(|&c| match c {
54 0x20..=0x5F => c as char,
55 0xC1..=0xDA => (c - 0x80) as char,
56 _ => '?',
57 })
58 .collect()
59}
60
61pub fn ascii_to_petscii(ascii: &str) -> Vec<u8> {
62 ascii
63 .chars()
64 .map(|c| match c {
65 ' '..='_' => c as u8,
66 'a'..='z' => (c as u8) - 32,
67 _ => 0x3F,
68 })
69 .collect()
70}
71
72impl D64 {
73 pub fn new(tracks: u8) -> Result<Self, D64Error> {
74 if tracks != 35 && tracks != 40 {
75 return Err(D64Error::InvalidFileSize);
76 }
77 let size = if tracks == 35 {
78 D64_35_TRACKS_SIZE
79 } else {
80 D64_40_TRACKS_SIZE
81 };
82 Ok(Self {
83 data: vec![0; size],
84 tracks,
85 })
86 }
87
88 pub fn format(&mut self, disk_name: &str, disk_id: &str) -> Result<(), D64Error> {
89 self.data.fill(0);
90
91 let mut bam = [0u8; 256];
92 bam[0] = 18;
93 bam[1] = 1;
94 bam[2] = 0x41;
95
96 for track in 1..=self.tracks {
97 let track_idx = (track - 1) as usize;
98 let sectors = SECTORS_PER_TRACK[track_idx];
99 bam[4 + track_idx * 4] = sectors;
100 bam[5 + track_idx * 4] = 0xFF;
101 bam[6 + track_idx * 4] = 0xFF;
102 bam[7 + track_idx * 4] = if sectors > 16 {
103 0xFF
104 } else {
105 (1 << sectors) - 1
106 };
107 }
108
109 for track in 18..=19 {
110 let track_idx = (track - 1) as usize;
111 bam[4 + track_idx * 4] = 0;
112 bam[5 + track_idx * 4] = 0;
113 bam[6 + track_idx * 4] = 0;
114 bam[7 + track_idx * 4] = 0;
115 }
116
117 let disk_name_bytes = ascii_to_petscii(disk_name);
118 let disk_id_bytes = ascii_to_petscii(disk_id);
119 bam[144..144 + disk_name_bytes.len()].copy_from_slice(&disk_name_bytes);
120 bam[162..164].copy_from_slice(&disk_id_bytes);
121
122 self.write_sector(18, 0, &bam)?;
123
124 let mut dir = [0u8; 256];
125 dir[1] = 0xFF;
126 self.write_sector(18, 1, &dir)?;
127
128 Ok(())
129 }
130
131 pub fn from_file(path: &str) -> Result<Self, D64Error> {
132 let mut file = File::open(path)?;
133 let mut data = Vec::new();
134 file.read_to_end(&mut data)?;
135
136 let tracks = match data.len() {
137 D64_35_TRACKS_SIZE => 35,
138 D64_40_TRACKS_SIZE => 40,
139 _ => return Err(D64Error::InvalidFileSize),
140 };
141
142 Ok(Self { data, tracks })
143 }
144
145 pub fn save_to_file(&self, path: &str) -> Result<(), D64Error> {
146 let mut file = File::create(path)?;
147 file.write_all(&self.data)?;
148 Ok(())
149 }
150
151 pub fn read_sector(&self, track: u8, sector: u8) -> Result<&[u8], D64Error> {
152 let offset = self.sector_offset(track, sector)?;
153 Ok(&self.data[offset..offset + 256])
154 }
155
156 pub fn write_sector(&mut self, track: u8, sector: u8, data: &[u8]) -> Result<(), D64Error> {
157 let offset = self.sector_offset(track, sector)?;
158 self.data[offset..offset + 256].copy_from_slice(data);
159 Ok(())
160 }
161
162 pub fn trace_file(&self, filename: &str) -> Result<Vec<(u8, u8)>, D64Error> {
163 let (start_track, start_sector) = self.find_file(filename)?;
164 let mut sectors = Vec::new();
165 let mut track = start_track;
166 let mut sector = start_sector;
167
168 loop {
169 sectors.push((track, sector));
170 let data = self.read_sector(track, sector)?;
171 let next_track = data[0];
172 let next_sector = data[1];
173
174 if next_track == 0 {
175 break;
176 }
177 track = next_track;
178 sector = next_sector;
179 }
180
181 Ok(sectors)
182 }
183
184 fn sector_offset(&self, track: u8, sector: u8) -> Result<usize, D64Error> {
185 if track == 0 || track > self.tracks || sector >= SECTORS_PER_TRACK[(track - 1) as usize] {
186 return Err(D64Error::InvalidTrackSector);
187 }
188
189 let mut offset = 0;
190 for t in 1..track {
191 offset += SECTORS_PER_TRACK[(t - 1) as usize] as usize * 256;
192 }
193 offset += sector as usize * 256;
194
195 Ok(offset)
196 }
197
198 pub fn list_files(&self) -> Result<Vec<String>, D64Error> {
199 let mut files = Vec::new();
200 let dir_track = 18;
201 let mut sector = 1;
202 let mut visited_sectors = std::collections::HashSet::new();
203
204 loop {
205 if visited_sectors.contains(&(dir_track, sector)) {
206 return Err(D64Error::InvalidTrackSector);
207 }
208 visited_sectors.insert((dir_track, sector));
209
210 let data = self.read_sector(dir_track, sector)?;
211
212 for i in (0..256).step_by(32) {
213 let file_type = data[i + 2];
214 if file_type == 0 {
215 continue;
216 }
217 if file_type != 0 && file_type & 0x07 != 0 {
218 let name_end = data[i + 5..i + 21]
219 .iter()
220 .position(|&x| x == 0xA0)
221 .unwrap_or(16);
222 let name = petscii_to_ascii(&data[i + 5..i + 5 + name_end]);
223 files.push(name);
224 }
225 }
226
227 let next_track = data[0];
228 let next_sector = data[1];
229
230 if next_track == 0 || (next_track == 18 && next_sector == 1) {
231 break;
232 }
233
234 if next_track != 18 || next_sector >= SECTORS_PER_TRACK[17] {
235 return Err(D64Error::InvalidTrackSector);
236 }
237
238 sector = next_sector;
239 }
240
241 Ok(files)
242 }
243
244 pub fn extract_file(&self, filename: &str) -> Result<Vec<u8>, D64Error> {
245 let (start_track, start_sector) = self.find_file(filename)?;
246 let mut content = Vec::new();
247 let mut track = start_track;
248 let mut sector = start_sector;
249
250 loop {
251 let data = self.read_sector(track, sector)?;
252 let next_track = data[0];
253 let next_sector = data[1];
254 let bytes_to_read = if next_track == 0 { next_sector } else { 254 };
255 content.extend_from_slice(&data[2..2 + bytes_to_read as usize]);
256
257 if next_track == 0 {
258 break;
259 }
260 track = next_track;
261 sector = next_sector;
262 }
263
264 Ok(content)
265 }
266
267 pub fn insert_file(&mut self, filename: &str, content: &[u8]) -> Result<(), D64Error> {
268 let (mut track, mut sector) = self.find_free_sector()?;
269 let mut remaining = content;
270
271 let dir_entry = self.create_dir_entry(filename, track, sector)?;
272 self.write_dir_entry(dir_entry)?;
273
274 while !remaining.is_empty() {
275 let mut sector_data = vec![0; 256];
276 let (next_track, next_sector) = if remaining.len() > 254 {
277 sector_data[0] = track;
278 sector_data[1] = sector + 1;
279 if sector + 1 >= SECTORS_PER_TRACK[(track - 1) as usize] {
280 (track + 1, 0)
281 } else {
282 (track, sector + 1)
283 }
284 } else {
285 sector_data[0] = 0;
286 sector_data[1] = remaining.len() as u8;
287 (0, 0)
288 };
289
290 let bytes_to_write = remaining.len().min(254);
291 sector_data[2..2 + bytes_to_write].copy_from_slice(&remaining[..bytes_to_write]);
292 self.write_sector(track, sector, §or_data)?;
293
294 remaining = &remaining[bytes_to_write..];
295 track = next_track;
296 sector = next_sector;
297
298 if track == 0 {
299 break;
300 }
301 }
302
303 Ok(())
304 }
305
306 fn find_file(&self, filename: &str) -> Result<(u8, u8), D64Error> {
307 let dir_track = 18;
308 let mut sector = 1;
309
310 loop {
311 let data = self.read_sector(dir_track, sector)?;
312 for i in (0..256).step_by(32) {
313 let file_type = data[i + 2];
314 if file_type != 0 && file_type & 0x07 != 0 {
315 let name = petscii_to_ascii(&data[i + 5..i + 21]);
316 if name.trim() == filename {
317 return Ok((data[i + 3], data[i + 4]));
318 }
319 }
320 }
321 sector = data[1];
322 if sector == 0 {
323 break;
324 }
325 }
326
327 Err(D64Error::FileNotFound)
328 }
329
330 pub fn read_bam(&self) -> Result<BAM, D64Error> {
331 let bam_data = self.read_sector(18, 0)?;
332 BAM::from_sector_data(bam_data, self.tracks)
333 }
334
335 pub fn write_bam(&mut self, bam: &BAM) -> Result<(), D64Error> {
336 let bam_data = bam.to_sector_data();
337 self.write_sector(18, 0, &bam_data)
338 }
339
340 pub fn allocate_sector(&mut self, track: u8, sector: u8) -> Result<(), D64Error> {
341 let mut bam = self.read_bam()?;
342 bam.allocate_sector(track, sector)?;
343 self.write_bam(&bam)
344 }
345
346 pub fn free_sector(&mut self, track: u8, sector: u8) -> Result<(), D64Error> {
347 let mut bam = self.read_bam()?;
348 bam.free_sector(track, sector)?;
349 self.write_bam(&bam)
350 }
351
352 pub fn find_free_sector(&self) -> Result<(u8, u8), D64Error> {
353 let bam = self.read_bam()?;
354 for track in 1..=self.tracks {
355 if let Some(sector) = bam.find_free_sector(track) {
356 return Ok((track, sector));
357 }
358 }
359 Err(D64Error::DiskFull)
360 }
361
362 fn create_dir_entry(
363 &self,
364 filename: &str,
365 track: u8,
366 sector: u8,
367 ) -> Result<[u8; 32], D64Error> {
368 let mut entry = [0u8; 32];
369 entry[2] = 0x82;
370 entry[3] = track;
371 entry[4] = sector;
372 let name_bytes = ascii_to_petscii(filename);
373 entry[5..5 + name_bytes.len()].copy_from_slice(&name_bytes);
374 Ok(entry)
375 }
376
377 fn write_dir_entry(&mut self, entry: [u8; 32]) -> Result<(), D64Error> {
378 let dir_track = 18;
379 let mut sector = 1;
380
381 loop {
382 let mut data = self.read_sector(dir_track, sector)?.to_vec();
383 for i in (0..256).step_by(32) {
384 if data[i + 2] == 0 {
385 data[i..i + 32].copy_from_slice(&entry);
386 self.write_sector(dir_track, sector, &data)?;
387 return Ok(());
388 }
389 }
390 sector = data[1];
391 if sector == 0 {
392 return Err(D64Error::DiskFull);
393 }
394 }
395 }
396}
397
398impl BAM {
399 fn from_sector_data(data: &[u8], tracks: u8) -> Result<Self, D64Error> {
400 let mut bam = BAM {
401 tracks,
402 free_sectors: [0; 40],
403 bitmap: [[0; 3]; 40],
404 disk_name: [0; 16],
405 disk_id: [0; 2],
406 dos_type: data[2],
407 };
408
409 for track in 0..tracks as usize {
410 bam.free_sectors[track] = data[4 + track * 4];
411 bam.bitmap[track][0] = data[5 + track * 4];
412 bam.bitmap[track][1] = data[6 + track * 4];
413 bam.bitmap[track][2] = data[7 + track * 4];
414 }
415
416 bam.disk_name.copy_from_slice(&data[144..160]);
417 bam.disk_id.copy_from_slice(&data[162..164]);
418
419 Ok(bam)
420 }
421
422 fn to_sector_data(&self) -> Vec<u8> {
423 let mut data = vec![0; 256];
424 data[0] = 18;
425 data[1] = 1;
426 data[2] = self.dos_type;
427
428 for track in 0..self.tracks as usize {
429 data[4 + track * 4] = self.free_sectors[track];
430 data[5 + track * 4] = self.bitmap[track][0];
431 data[6 + track * 4] = self.bitmap[track][1];
432 data[7 + track * 4] = self.bitmap[track][2];
433 }
434
435 data[144..160].copy_from_slice(&self.disk_name);
436 data[162..164].copy_from_slice(&self.disk_id);
437
438 data
439 }
440
441 pub fn allocate_sector(&mut self, track: u8, sector: u8) -> Result<(), D64Error> {
442 if track == 0 || track > self.tracks || sector >= SECTORS_PER_TRACK[(track - 1) as usize] {
443 return Err(D64Error::InvalidTrackSector);
444 }
445
446 let track_idx = (track - 1) as usize;
447 let byte_idx = (sector / 8) as usize;
448 let bit_idx = sector % 8;
449
450 if self.bitmap[track_idx][byte_idx] & (1 << bit_idx) == 0 {
451 return Ok(());
452 }
453
454 self.bitmap[track_idx][byte_idx] &= !(1 << bit_idx);
455 self.free_sectors[track_idx] -= 1;
456
457 Ok(())
458 }
459
460 pub fn free_sector(&mut self, track: u8, sector: u8) -> Result<(), D64Error> {
461 if track == 0 || track > self.tracks || sector >= SECTORS_PER_TRACK[(track - 1) as usize] {
462 return Err(D64Error::InvalidTrackSector);
463 }
464
465 let track_idx = (track - 1) as usize;
466 let byte_idx = (sector / 8) as usize;
467 let bit_idx = sector % 8;
468
469 if self.bitmap[track_idx][byte_idx] & (1 << bit_idx) != 0 {
470 return Ok(());
471 }
472
473 self.bitmap[track_idx][byte_idx] |= 1 << bit_idx;
474 self.free_sectors[track_idx] += 1;
475
476 Ok(())
477 }
478
479 pub fn find_free_sector(&self, track: u8) -> Option<u8> {
480 if track == 0 || track > self.tracks {
481 return None;
482 }
483
484 let track_idx = (track - 1) as usize;
485 for (byte_idx, &byte) in self.bitmap[track_idx].iter().enumerate() {
486 if byte != 0 {
487 for bit_idx in 0..8 {
488 if byte & (1 << bit_idx) != 0 {
489 let sector = (byte_idx as u8) * 8 + bit_idx;
490 if sector < SECTORS_PER_TRACK[track_idx] {
491 return Some(sector);
492 }
493 }
494 }
495 }
496 }
497 None
498 }
499
500 pub fn get_free_sectors_count(&self, track: u8) -> Result<u8, D64Error> {
501 if track == 0 || track > self.tracks {
502 return Err(D64Error::InvalidTrackSector);
503 }
504 Ok(self.free_sectors[(track - 1) as usize])
505 }
506
507 pub fn get_disk_name(&self) -> String {
508 petscii_to_ascii(&self.disk_name)
509 }
510
511 pub fn get_disk_id(&self) -> String {
512 petscii_to_ascii(&self.disk_id)
513 }
514
515 pub fn set_disk_name(&mut self, name: &str) {
516 let name_bytes = ascii_to_petscii(name);
517 self.disk_name[..name_bytes.len()].copy_from_slice(&name_bytes);
518 self.disk_name[name_bytes.len()..].fill(0xA0);
519 }
520
521 pub fn set_disk_id(&mut self, id: &str) {
522 let id_bytes = ascii_to_petscii(id);
523 self.disk_id.copy_from_slice(&id_bytes[..2]);
524 }
525}