Skip to content

Commit

Permalink
Implement image loader for PRI
Browse files Browse the repository at this point in the history
  • Loading branch information
twvd committed Nov 22, 2024
1 parent ad865d0 commit 511c0ee
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Supported floppy image formats:
* Applesauce A2R 2.x and 3.x (flux)
* Applesauce MOOF (bitstream and flux)
* PCE Flux Image (PFI, flux)
* PCE Raw Image (PRI, bitstream)
* Raw images (sector-based)

## Building and running from source
Expand Down
12 changes: 8 additions & 4 deletions floppy/src/loaders/auto.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
//! Auto-detect image file type and load
use crate::loaders::A2Rv2;
use crate::{
loaders::{Bitfile, Diskcopy42, FloppyImageLoader, Moof, RawImage},
loaders::{A2Rv2, A2Rv3, Bitfile, Diskcopy42, FloppyImageLoader, Moof, RawImage, PFI, PRI},
FloppyImage, FloppyType,
};

use anyhow::{bail, Result};
use strum::{Display, IntoEnumIterator};

use super::{A2Rv3, PFI};

/// Types of supported floppy images
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Display, Copy, Clone)]
pub enum ImageType {
Expand All @@ -20,6 +17,7 @@ pub enum ImageType {
Bitfile,
DC42,
PFI,
PRI,
Raw,
}

Expand All @@ -32,6 +30,7 @@ impl ImageType {
Self::Bitfile => "Bitfile",
Self::DC42 => "Apple DiskCopy 4.2",
Self::PFI => "PCE PFI",
Self::PRI => "PCE PRI",
Self::Raw => "Raw image",
}
}
Expand All @@ -57,6 +56,10 @@ impl Autodetect {
if data.len() >= 4 && data[0..4] == *b"PFI " {
return Ok(ImageType::PFI);
}
// PFI
if data.len() >= 4 && data[0..4] == *b"PRI " {
return Ok(ImageType::PRI);
}
// Bitfile / 'Dave format'
if data.len() >= 10
&& data[0..4] == data[4..8]
Expand Down Expand Up @@ -87,6 +90,7 @@ impl FloppyImageLoader for Autodetect {
ImageType::Bitfile => Bitfile::load(data, filename),
ImageType::DC42 => Diskcopy42::load(data, filename),
ImageType::PFI => PFI::load(data, filename),
ImageType::PRI => PRI::load(data, filename),
ImageType::Raw => RawImage::load(data, filename),
}
}
Expand Down
2 changes: 2 additions & 0 deletions floppy/src/loaders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod bitfile;
mod diskcopy42;
mod moof;
mod pfi;
mod pri;
mod raw;

use std::path::Path;
Expand All @@ -17,6 +18,7 @@ pub use bitfile::Bitfile;
pub use diskcopy42::Diskcopy42;
pub use moof::Moof;
pub use pfi::PFI;
pub use pri::PRI;
pub use raw::RawImage;

use crate::FloppyImage;
Expand Down
183 changes: 183 additions & 0 deletions floppy/src/loaders/pri.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
//! PCE PRI format
//! Bitstream format
//! http://www.hampa.ch/pce/index.html
use std::collections::HashMap;
use std::io::{Seek, SeekFrom};

use super::FloppyImageLoader;
use crate::{Floppy, FloppyImage, FloppyType, OriginalTrackType, TrackLength};

use anyhow::{bail, Result};
use binrw::io::Cursor;
use binrw::{binrw, BinRead};
use log::*;

const CRC_PRI: crc::Algorithm<u32> = crc::Algorithm {
width: 32,
poly: 0x1edc6f41,
init: 0,
refin: false,
refout: false,
xorout: 0,
check: 0,
residue: 0,
};

/// Standardized chunk header
#[binrw]
#[brw(big)]
#[derive(Debug)]
struct ChunkHeader {
/// ASCII chunk identifier
pub id: [u8; 4],

/// Chunk size in bytes
pub size: u32,
}

/// Standardized chunk tail
#[binrw]
#[brw(big)]
#[derive(Debug)]
struct ChunkTail {
pub crc: u32,
}

/// File header chunk payload
#[binrw]
#[brw(big)]
#[derive(Debug)]
struct PayloadHeader {
pub version: u8,
}

/// Track header
#[binrw]
#[brw(big)]
#[derive(Debug)]
struct PayloadTrack {
pub track: u32,
pub head: u32,
pub length: u32,
pub clock: u32,
}

/// PCE PRI image format loader
pub struct PRI {}

impl FloppyImageLoader for PRI {
fn load(data: &[u8], filename: Option<&str>) -> Result<FloppyImage> {
let mut cursor = Cursor::new(data);

let mut tracks: HashMap<(usize, usize), (usize, &[u8])> = HashMap::new();
let mut cur_track = 0;
let mut cur_side = 0;
let mut cur_len = 0;
let mut saw_header = false;

// Parse chunks from file
while let Ok(chunk) = ChunkHeader::read(&mut cursor) {
let startpos = cursor.position();

// Check CRC of the entire chunk
let checksum = crc::Crc::<u32>::new(&CRC_PRI).checksum(
&data[(startpos as usize - 8)..(startpos as usize + chunk.size as usize)],
);
let chunk_checksum = u32::from_be_bytes(
data[(startpos as usize + chunk.size as usize)
..(startpos as usize + chunk.size as usize + 4)]
.try_into()?,
);
if checksum != chunk_checksum {
bail!(
"Checksum for chunk '{}' incorrect, saw {:08X}, expected {:08X}",
String::from_utf8_lossy(&chunk.id),
chunk_checksum,
checksum
);
}

if &chunk.id != b"PRI " && !saw_header {
bail!("File header not found");
}

match &chunk.id {
b"PRI " => {
saw_header = true;
}
b"TRAK" => {
let payload = PayloadTrack::read(&mut cursor)?;

if payload.clock != 500_000 {
bail!(
"Unsupported clock rate {} on side {} track {}",
payload.clock,
cur_side,
cur_track
);
}
cur_track = payload.track as usize;
cur_side = payload.head as usize;
cur_len = payload.length as usize;
}
b"TEXT" => (),
b"DATA" => {
tracks.insert(
(cur_side, cur_track),
(
cur_len,
&data[(startpos as usize + 8)
..(startpos as usize + chunk.size as usize)],
),
);
}
b"BCLK" => {
bail!(
"BCLK encountered on side {}, track {}. Currently unsupported",
cur_side,
cur_track
);
}
b"END " => break,
_ => {
warn!(
"Found unsupported chunk '{}', skipping",
String::from_utf8_lossy(&chunk.id)
);
}
}

// Always consume the amount of bytes the chunk header reports
cursor.seek(SeekFrom::Start(startpos + u64::from(chunk.size) + 4))?;
}

let mut img = FloppyImage::new_empty(
if tracks.keys().any(|&(s, _t)| s > 0) {
FloppyType::Mac800K
} else {
FloppyType::Mac400K
},
filename.unwrap_or_default(),
);

// Fill tracks
for ((side, track), (bitlen, data)) in tracks {
if let TrackLength::Bits(t) = img.get_track_length(side, track) {
if t > 0 {
// Multiple captures encountered, we just use the first and hope it's good.
continue;
}
}
img.origtracktype[side][track] = OriginalTrackType::Bitstream;
img.set_actual_track_length(side, track, bitlen);

// The DATA chunk may be shorter than the track length in the
// preceding TRAK chunk suggests. In that case the remainder of
// the track data should be set to 0.
img.trackdata[side][track][0..data.len()].copy_from_slice(data);
}

Ok(img)
}
}

0 comments on commit 511c0ee

Please sign in to comment.