diff --git a/lib/disk/dev.go b/lib/disk/dev.go new file mode 100644 index 0000000..91a9ce7 --- /dev/null +++ b/lib/disk/dev.go @@ -0,0 +1,67 @@ +// Copyright © 2017 Zellyn Hunter + +// dev.go contains logic for reading ".po" disk images. + +package disk + +import ( + "fmt" + "io" + "io/ioutil" +) + +// Dev represents a .po disk image. +type Dev struct { + data []byte // The actual data in the file + blocks uint16 // Number of blocks +} + +var _ BlockDevice = (*Dev)(nil) + +// LoadDev loads a .po image from a file. +func LoadDev(filename string) (Dev, error) { + bb, err := ioutil.ReadFile(filename) + if err != nil { + return Dev{}, err + } + if len(bb)%512 != 0 { + return Dev{}, fmt.Errorf("expected file %q to contain a multiple of 512 bytes, but got %d", filename, len(bb)) + } + return Dev{ + data: bb, + blocks: uint16(len(bb) / 512), + }, nil +} + +// Empty creates a .po image that is all zeros. +func EmptyDev(blocks uint16) Dev { + return Dev{ + data: make([]byte, 512*int(blocks)), + blocks: blocks, + } +} + +// ReadBlock reads a single block from the device. It always returns +// 512 byes. +func (d Dev) ReadBlock(index uint16) (Block, error) { + var b Block + copy(b[:], d.data[int(index)*512:int(index+1)*512]) + return b, nil +} + +// WriteBlock writes a single block to a device. It expects exactly +// 512 bytes. +func (d Dev) WriteBlock(index uint16, data Block) error { + copy(d.data[int(index)*512:int(index+1)*512], data[:]) + return nil +} + +// Blocks returns the number of blocks in the device. +func (d Dev) Blocks() uint16 { + return d.blocks +} + +// Write writes the device contents to the given file. +func (d Dev) Write(w io.Writer) (n int, err error) { + return w.Write(d.data) +} diff --git a/lib/disk/disk.go b/lib/disk/disk.go index 7d5d222..2d45358 100644 --- a/lib/disk/disk.go +++ b/lib/disk/disk.go @@ -166,15 +166,125 @@ func OpenDisk(filename string) (SectorDisk, error) { return nil, fmt.Errorf("Unimplemented/unknown disk file extension %q", ext) } +// OpenDev opens a device image by filename. +func OpenDev(filename string) (BlockDevice, error) { + ext := strings.ToLower(path.Ext(filename)) + switch ext { + case ".po": + return LoadDev(filename) + } + return nil, fmt.Errorf("Unimplemented/unknown device file extension %q", ext) +} + // Open opens a disk image by filename, returning an Operator. func Open(filename string) (Operator, error) { sd, err := OpenDisk(filename) - if err != nil { - return nil, err + if err == nil { + var op Operator + op, err = OperatorForDisk(sd) + if err == nil { + return op, nil + } } - op, err := OperatorForDisk(sd) - if err != nil { - return nil, err + + dev, err2 := OpenDev(filename) + if err2 == nil { + var op Operator + op, err2 = OperatorForDevice(dev) + if err2 != nil { + return nil, err2 + } + return op, nil } - return op, nil + + return nil, err +} + +type DiskBlockDevice struct { + lsd LogicalSectorDisk + blocks uint16 +} + +// BlockDeviceFromSectorDisk creates a ProDOS block device from a +// SectorDisk. It reads maps ProDOS to physical sectors. +func BlockDeviceFromSectorDisk(sd SectorDisk) (BlockDevice, error) { + lsd, err := NewMappedDisk(sd, ProDOSLogicalToPhysicalSectorMap) + if err != nil { + return nil, err + } + + return DiskBlockDevice{ + lsd: lsd, + blocks: uint16(lsd.Tracks()) / 2 * uint16(lsd.Sectors()), + }, nil +} + +// ReadBlock reads a single block from the device. It always returns +// 512 byes. +func (dbv DiskBlockDevice) ReadBlock(index uint16) (Block, error) { + var b Block + if index >= dbv.blocks { + return b, fmt.Errorf("device has %d blocks; tried to read block %d (index=%d)", dbv.blocks, index+1, index) + } + i := int(index) * 2 + sectors := int(dbv.lsd.Sectors()) + + track0 := i / sectors + sector0 := i % sectors + sector1 := sector0 + 1 + track1 := track0 + if sector1 == sectors { + sector1 = 0 + track1++ + } + + b0, err := dbv.lsd.ReadLogicalSector(byte(track0), byte(sector0)) + if err != nil { + return b, fmt.Errorf("error reading first half of block %d (t:%d s:%d): %v", index, track0, sector0, err) + } + b1, err := dbv.lsd.ReadLogicalSector(byte(track1), byte(sector1)) + if err != nil { + return b, fmt.Errorf("error reading second half of block %d (t:%d s:%d): %v", index, track1, sector1, err) + } + copy(b[:256], b0) + copy(b[256:], b1) + return b, nil +} + +// WriteBlock writes a single block to a device. It expects exactly +// 512 bytes. +func (dbv DiskBlockDevice) WriteBlock(index uint16, data Block) error { + if index >= dbv.blocks { + return fmt.Errorf("device has %d blocks; tried to read block %d (index=%d)", dbv.blocks, index+1, index) + } + i := int(index) * 2 + sectors := int(dbv.lsd.Sectors()) + + track0 := i / sectors + sector0 := i % sectors + sector1 := sector0 + 1 + track1 := track0 + if sector1 == sectors { + sector1 = 0 + track1++ + } + + if err := dbv.lsd.WriteLogicalSector(byte(track0), byte(sector0), data[:256]); err != nil { + return fmt.Errorf("error writing first half of block %d (t:%d s:%d): %v", index, track0, sector0, err) + } + if err := dbv.lsd.WriteLogicalSector(byte(track1), byte(sector1), data[256:]); err != nil { + return fmt.Errorf("error writing second half of block %d (t:%d s:%d): %v", index, track1, sector1, err) + } + + return nil +} + +// Blocks returns the number of blocks on the device. +func (dbv DiskBlockDevice) Blocks() uint16 { + return dbv.blocks +} + +// Write writes the device contents to the given Writer. +func (dbv DiskBlockDevice) Write(w io.Writer) (int, error) { + return dbv.lsd.Write(w) } diff --git a/lib/disk/ops.go b/lib/disk/ops.go index eac9704..ff508e2 100644 --- a/lib/disk/ops.go +++ b/lib/disk/ops.go @@ -91,3 +91,41 @@ func OperatorForDisk(sd SectorDisk) (Operator, error) { sort.Strings(names) return nil, fmt.Errorf("Cannot find a disk operator matching the given disk image (tried %s)", strings.Join(names, ", ")) } + +// deviceOperatorFactory is the type of functions that accept a BlockDevice, +// and may return an Operator interface to operate on it. +type deviceOperatorFactory func(BlockDevice) (Operator, error) + +// deviceOperatorFactories is the map of currently-registered device +// operator factories. +var deviceOperatorFactories map[string]deviceOperatorFactory + +func init() { + deviceOperatorFactories = make(map[string]deviceOperatorFactory) +} + +// RegisterDeviceOperatorFactory registers a device operator factory with +// the given name: a function that accepts a BlockDevice, and may +// return an Operator. It doesn't lock deviceOperatorFactories: it is +// expected to be called only from package `init` functions. +func RegisterDeviceOperatorFactory(name string, factory deviceOperatorFactory) { + deviceOperatorFactories[name] = factory +} + +// OperatorForDevice returns an Operator for the given BlockDevice, if possible. +func OperatorForDevice(sd BlockDevice) (Operator, error) { + if len(deviceOperatorFactories) == 0 { + return nil, errors.New("Cannot find an operator matching the given device image (none registered)") + } + for _, factory := range deviceOperatorFactories { + if operator, err := factory(sd); err == nil { + return operator, nil + } + } + names := make([]string, 0, len(deviceOperatorFactories)) + for name := range deviceOperatorFactories { + names = append(names, `"`+name+`"`) + } + sort.Strings(names) + return nil, fmt.Errorf("Cannot find a device operator matching the given device image (tried %s)", strings.Join(names, ", ")) +} diff --git a/lib/prodos/prodos.go b/lib/prodos/prodos.go index 72caa51..9e9a1c1 100644 --- a/lib/prodos/prodos.go +++ b/lib/prodos/prodos.go @@ -590,3 +590,29 @@ func (o operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool, func (o operator) Write(w io.Writer) (int, error) { return o.dev.Write(w) } + +// deviceOperatorFactory is the factory that returns prodos operators +// given device images. +func deviceOperatorFactory(bd disk.BlockDevice) (disk.Operator, error) { + op := operator{dev: bd} + _, err := op.Catalog("") + if err != nil { + return nil, fmt.Errorf("Cannot read catalog. Underlying error: %v", err) + } + return op, nil +} + +// diskOperatorFactory is the factory that returns dos3 operators +// given disk images. +func diskOperatorFactory(sd disk.SectorDisk) (disk.Operator, error) { + bd, err := disk.BlockDeviceFromSectorDisk(sd) + if err != nil { + return nil, err + } + return deviceOperatorFactory(bd) +} + +func init() { + disk.RegisterDeviceOperatorFactory(operatorName, deviceOperatorFactory) + disk.RegisterDiskOperatorFactory(operatorName, diskOperatorFactory) +} diff --git a/main.go b/main.go index ca9cd99..2cf22f0 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( // Import disk operator factories for DOS3 and Super-Mon _ "github.com/zellyn/diskii/lib/dos3" + _ "github.com/zellyn/diskii/lib/prodos" _ "github.com/zellyn/diskii/lib/supermon" )