diff --git a/lib/disk/disk.go b/lib/disk/disk.go index fc4e3c9..18f0143 100644 --- a/lib/disk/disk.go +++ b/lib/disk/disk.go @@ -34,14 +34,28 @@ var Dos33PhysicalToLogicalSectorMap = []byte{ 0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F, } +// ProDOSLogicalToPhysicalSectorMap maps logical sector numbers to pysical ones. +// See [UtA2e 9-43 - Sectors vs. Blocks]. +var ProDOSLogicalToPhysicalSectorMap = []byte{ + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, + 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, +} + +// ProDosPhysicalToLogicalSectorMap maps physical sector numbers to logical ones. +// See [UtA2e 9-43 - Sectors vs. Blocks]. +var ProDosPhysicalToLogicalSectorMap = []byte{ + 0x00, 0x08, 0x01, 0x09, 0x02, 0x0A, 0x03, 0x0B, + 0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F, +} + // TrackSector is a pair of track/sector bytes. type TrackSector struct { Track byte Sector byte } -// SectorDisk is the interface use to read and write disks by physical -// (matches sector header) sector number. +// SectorDisk is the interface used to read and write disks by +// physical (matches sector header) sector number. type SectorDisk interface { // ReadPhysicalSector reads a single physical sector from the disk. It // always returns 256 byes. diff --git a/lib/prodos/prodos.go b/lib/prodos/prodos.go new file mode 100644 index 0000000..2d00cca --- /dev/null +++ b/lib/prodos/prodos.go @@ -0,0 +1,175 @@ +// Copyright © 2017 Zellyn Hunter + +// Package prodos contains routines for working with the on-disk +// structures of Apple ProDOS. +package prodos + +import ( + "fmt" + "io" +) + +// A single ProDOS block. +type Block [512]byte + +// BlockDevice is the interface used to read and write devices by +// logical block number. +type BlockDevice interface { + // ReadBlock reads a single block from the device. It always returns + // 512 byes. + ReadBlock(index uint16) (Block, error) + // WriteBlock writes a single block to a device. It expects exactly + // 512 bytes. + WriteBlock(index uint16, data Block) error + // Blocks returns the number of blocks on the device. + Blocks() uint16 + // Write writes the device contents to the given Writer. + Write(io.Writer) (int, error) +} + +type VolumeBitMap []Block + +func NewVolumeBitMap(blocks uint16) VolumeBitMap { + vbm := VolumeBitMap(make([]Block, (blocks+(512*8)-1)/(512*8))) + for b := 0; b < int(blocks); b++ { + vbm.MarkUnused(uint16(b)) + } + return vbm +} + +func (vbm VolumeBitMap) MarkUsed(block uint16) { + vbm.mark(block, false) +} + +func (vbm VolumeBitMap) MarkUnused(block uint16) { + vbm.mark(block, true) +} + +func (vbm VolumeBitMap) mark(block uint16, set bool) { + byteIndex := block >> 3 + blockIndex := byteIndex / 512 + blockByteIndex := byteIndex % 512 + bit := byte(1 << (7 - (block & 7))) + if set { + vbm[blockIndex][blockByteIndex] |= bit + } else { + vbm[blockIndex][blockByteIndex] &^= bit + } +} + +func (vbm VolumeBitMap) Free(block uint16) bool { + byteIndex := block >> 3 + blockIndex := byteIndex / 512 + blockByteIndex := byteIndex % 512 + bit := byte(1 << (7 - (block & 7))) + return vbm[blockIndex][blockByteIndex]&bit > 0 +} + +// ReadVolumeBitMap +func ReadVolumeBitMap(bd BlockDevice, startBlock uint16) (VolumeBitMap, error) { + blocks := bd.Blocks() / 4096 + vbm := make([]Block, blocks) + for i := uint16(0); i < blocks; i++ { + block, err := bd.ReadBlock(startBlock + i) + if err != nil { + return nil, fmt.Errorf("cannot read block %d of Volume Bit Map: %v", err) + } + vbm[i] = block + } + return VolumeBitMap(vbm), nil +} + +// Write writes the Volume Bit Map to a block device, starting at the +// given block. +func (vbm VolumeBitMap) Write(bd BlockDevice, startBlock uint16) error { + for i, block := range vbm { + if err := bd.WriteBlock(startBlock+uint16(i), block); err != nil { + return fmt.Errorf("cannot write block %d of Volume Bit Map: %v", err) + } + } + return nil +} + +type DateTime struct { + YMD [2]byte + HM [2]byte +} + +// VolumeDirectoryKeyBlock is the struct used to hold the ProDOS Volume Directory Key +// Block structure. See page 4-4 of Beneath Apple ProDOS. +type VolumeDirectoryKeyBlock struct { + Prev uint16 // Pointer to previous block (always zero: the KeyBlock is the first Volume Directory block + Next uint16 // Pointer to next block in the Volume Directory + Header VolumeDirectoryHeader + Descriptors [12]FileDescriptor +} + +// VolumeDirectoryBlock is a normal (non-key) segment in the Volume Directory Header. +type VolumeDirectoryBlock struct { + Prev uint16 // Pointer to previous block in the Volume Directory. + Next uint16 // Pointer to next block in the Volume Directory. + Descriptors [13]FileDescriptor +} + +type VolumeDirectoryHeader struct { + TypeAndNameLength byte // Storage type (top four bits) and volume name length (lower four). + VolumeName [15]byte // Volume name (actual length defined in TypeAndNameLength) + Unused1 [8]byte + Creation DateTime // Date and time volume was formatted + Version byte + MinVersion byte + Access byte + EntryLength byte // Length of each entry in the Volume Directory: usually $27 + EntriesPerBlock byte // Usually $0D + FileCount uint16 // Number of active entries in the Volume Directory, not counting the Volume Directory Header + BitMapPointer uint16 // Block number of start of VolumeBitMap. Usually 6 + TotalBlocks uint16 // Total number of blocks on the device. $118 (280) for a 35-track diskette. +} + +type Access byte + +const ( + AccessReadable Access = 0x01 + AccessWritable Access = 0x02 + AccessChangedSinceBackup Access = 0x20 + AccessRenamable Access = 0x40 + AccessDestroyable Access = 0x80 +) + +// FileDescriptor is the entry in the volume directory for a file or +// subdirectory. +type FileDescriptor struct { + TypeAndNameLength byte // Storage type (top four bits) and volume name length (lower four) + FileName [15]byte // Filename (actual length defined in TypeAndNameLength) + FileType byte // ProDOS / SOS filetype + KeyPointer uint16 // block number of key block for file + BlocksUsed uint16 // Total number of blocks used including index blocks and data blocks. For a subdirectory, the number of directory blocks + Eof [3]byte // 3-byte offset of EOF from first byte. For sequential files, just the length + Creation DateTime // Date and time of of file creation + Version byte + MinVersion byte + Access Access + // For TXT files, random access record length (L from OPEN) + // For BIN files, load address for binary image (A from BSAVE) + // For BAS files, load address for program image (when SAVEd) + // For VAR files, address of compressed variables image (when STOREd) + // For SYS files, load address for system program (usually $2000) + AuxType uint16 + LastMod DateTime + HeaderPointer uint16 // Block number of the key block for the directory which describes this file. +} + +// An index block contains 256 16-bit block numbers, pointing to other +// blocks. The LSBs are stored in the first half, MSBs in the second. +type IndexBlock Block + +// Get the blockNum'th block number from an index block. +func (i IndexBlock) Get(blockNum byte) uint16 { + return uint16(i[blockNum]) + uint16(i[256+int(blockNum)])<<8 +} + +// Set the blockNum'th block number in an index block. +func (i IndexBlock) Set(blockNum byte, block uint16) { + i[blockNum] = byte(block) + i[256+int(blockNum)] = byte(block >> 8) +} diff --git a/lib/prodos/prodos_test.go b/lib/prodos/prodos_test.go new file mode 100644 index 0000000..c660cf8 --- /dev/null +++ b/lib/prodos/prodos_test.go @@ -0,0 +1 @@ +package prodos diff --git a/lib/prodos/testdata/ProDOS_2_4_1.dsk b/lib/prodos/testdata/ProDOS_2_4_1.dsk new file mode 100644 index 0000000..ef4e8b1 Binary files /dev/null and b/lib/prodos/testdata/ProDOS_2_4_1.dsk differ