mirror of
https://github.com/zellyn/diskii.git
synced 2024-06-08 13:29:28 +00:00
Add initial support for dos33 disk .dsk files
This commit is contained in:
parent
9ac6ab5962
commit
fe96da5d48
82
lib/disk/disk.go
Normal file
82
lib/disk/disk.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||||
|
|
||||||
|
// Package disk contains routines for reading and writing various disk
|
||||||
|
// file formats.
|
||||||
|
package disk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SectorDisk interface {
|
||||||
|
// ReadLogicalSector reads a single logical sector from the disk. It
|
||||||
|
// always returns 256 byes.
|
||||||
|
ReadLogicalSector(track byte, sector byte) ([]byte, error)
|
||||||
|
// WriteLogicalSector writes a single logical sector to a disk. It
|
||||||
|
// expects exactly 256 bytes.
|
||||||
|
WriteLogicalSector(track byte, sector byte, data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DOS33Tracks = 35 // Tracks per disk
|
||||||
|
DOS33Sectors = 16 // Sectors per track
|
||||||
|
// DOS33DiskBytes is the number of bytes on a DOS 3.3 disk.
|
||||||
|
DOS33DiskBytes = 143360 // 35 tracks * 16 sectors * 256 bytes
|
||||||
|
DOS33TrackBytes = 256 * DOS33Sectors // Bytes per track
|
||||||
|
)
|
||||||
|
|
||||||
|
// DSK represents a .dsk disk image.
|
||||||
|
type DSK struct {
|
||||||
|
data [DOS33DiskBytes]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ SectorDisk = (*DSK)(nil)
|
||||||
|
|
||||||
|
// LoadDSK loads a .dsk image from a file.
|
||||||
|
func LoadDSK(filename string) (DSK, error) {
|
||||||
|
d := DSK{}
|
||||||
|
bb, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return d, err
|
||||||
|
}
|
||||||
|
if len(bb) != DOS33DiskBytes {
|
||||||
|
return d, fmt.Errorf("Expected file %q to contain %d bytes, but got %d.", filename, DOS33DiskBytes, len(bb))
|
||||||
|
}
|
||||||
|
copy(d.data[:], bb)
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLogicalSector reads a single logical sector from the disk. It
|
||||||
|
// always returns 256 byes.
|
||||||
|
func (d DSK) ReadLogicalSector(track byte, sector byte) ([]byte, error) {
|
||||||
|
if track < 0 || track >= DOS33Tracks {
|
||||||
|
return nil, fmt.Errorf("Expected track between 0 and %d; got %d", DOS33Tracks-1, track)
|
||||||
|
}
|
||||||
|
if sector < 0 || sector >= DOS33Sectors {
|
||||||
|
return nil, fmt.Errorf("Expected sector between 0 and %d; got %d", DOS33Sectors-1, sector)
|
||||||
|
}
|
||||||
|
|
||||||
|
start := int(track)*DOS33TrackBytes + 256*int(sector)
|
||||||
|
buf := make([]byte, 256)
|
||||||
|
copy(buf, d.data[start:start+256])
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteLogicalSector writes a single logical sector to a disk. It
|
||||||
|
// expects exactly 256 bytes.
|
||||||
|
func (d DSK) WriteLogicalSector(track byte, sector byte, data []byte) error {
|
||||||
|
if track < 0 || track >= DOS33Tracks {
|
||||||
|
return fmt.Errorf("Expected track between 0 and %d; got %d", DOS33Tracks-1, track)
|
||||||
|
}
|
||||||
|
if sector < 0 || sector >= DOS33Sectors {
|
||||||
|
return fmt.Errorf("Expected sector between 0 and %d; got %d", DOS33Sectors-1, sector)
|
||||||
|
}
|
||||||
|
if len(data) != 256 {
|
||||||
|
return fmt.Errorf("WriteLogicalSector expects data of length 256; got %d", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
start := int(track)*DOS33TrackBytes + 256*int(sector)
|
||||||
|
copy(d.data[start:start+256], data)
|
||||||
|
return nil
|
||||||
|
}
|
49
lib/disk/marshal.go
Normal file
49
lib/disk/marshal.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||||
|
|
||||||
|
// marshal.go contains helpers for marshaling sector structs to/from
|
||||||
|
// disk.
|
||||||
|
|
||||||
|
package disk
|
||||||
|
|
||||||
|
// SectorSource is the interface for types that can marshal to sectors.
|
||||||
|
type SectorSource interface {
|
||||||
|
// ToSector marshals the sector struct to exactly 256 bytes.
|
||||||
|
ToSector() []byte
|
||||||
|
// GetTrack returns the track that a sector struct was loaded from.
|
||||||
|
GetTrack() byte
|
||||||
|
// GetSector returns the sector that a sector struct was loaded from.
|
||||||
|
GetSector() byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SectorSink is the interface for types that can unmarshal from sectors.
|
||||||
|
type SectorSink interface {
|
||||||
|
// FromSector unmarshals the sector struct from bytes. Input is
|
||||||
|
// expected to be exactly 256 bytes.
|
||||||
|
FromSector(data []byte)
|
||||||
|
// SetTrack sets the track that a sector struct was loaded from.
|
||||||
|
SetTrack(track byte)
|
||||||
|
// SetSector sets the sector that a sector struct was loaded from.
|
||||||
|
SetSector(sector byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalLogicalSector reads a sector from a SectorDisk, and
|
||||||
|
// unmarshals it into a SectorSink, setting its track and sector.
|
||||||
|
func UnmarshalLogicalSector(d SectorDisk, ss SectorSink, track, sector byte) error {
|
||||||
|
bytes, err := d.ReadLogicalSector(track, sector)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ss.FromSector(bytes)
|
||||||
|
ss.SetTrack(track)
|
||||||
|
ss.SetSector(sector)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalLogicalSector marshals a SectorSource to its sector on a
|
||||||
|
// SectorDisk.
|
||||||
|
func MarshalLogicalSector(d SectorDisk, ss SectorSource) error {
|
||||||
|
track := ss.GetTrack()
|
||||||
|
sector := ss.GetSector()
|
||||||
|
bytes := ss.ToSector()
|
||||||
|
return d.WriteLogicalSector(track, sector, bytes)
|
||||||
|
}
|
|
@ -6,13 +6,72 @@ package dos33
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zellyn/diskii/lib/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FreeSectorMap [4]byte // Bit map of free sectors in a track
|
const (
|
||||||
|
// VTOCTrack is the track on a DOS3.3 that holds the VTOC.
|
||||||
|
VTOCTrack = 17
|
||||||
|
// VTOCSector is the sector on a DOS3.3 that holds the VTOC.
|
||||||
|
VTOCSector = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type DiskSector struct {
|
||||||
|
Track byte
|
||||||
|
Sector byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTrack returns the track that a DiskSector was loaded from.
|
||||||
|
func (ds DiskSector) GetTrack() byte {
|
||||||
|
return ds.Track
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrack sets the track that a DiskSector was loaded from.
|
||||||
|
func (ds DiskSector) SetTrack(track byte) {
|
||||||
|
ds.Track = track
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSector returns the sector that a DiskSector was loaded from.
|
||||||
|
func (ds DiskSector) GetSector() byte {
|
||||||
|
return ds.Sector
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSector sets the sector that a DiskSector was loaded from.
|
||||||
|
func (ds DiskSector) SetSector(sector byte) {
|
||||||
|
ds.Sector = sector
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackFreeSectors maps the free sectors in a single track.
|
||||||
|
type TrackFreeSectors [4]byte // Bit map of free sectors in a track
|
||||||
|
|
||||||
|
// IsFree returns true if the given sector on a track is free (or if
|
||||||
|
// sector > 15).
|
||||||
|
func (t TrackFreeSectors) IsFree(sector byte) bool {
|
||||||
|
if sector >= 16 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bits := byte(1) << (sector % 8)
|
||||||
|
if sector < 8 {
|
||||||
|
return t[1]&bits > 0
|
||||||
|
}
|
||||||
|
return t[0]&bits > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnusedClear returns true if the unused bytes of the free sector map
|
||||||
|
// for a track are zeroes (as they're supposed to be).
|
||||||
|
func (t TrackFreeSectors) UnusedClear() bool {
|
||||||
|
return t[2] == 0 && t[3] == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiskFreeSectors maps the free sectors on a disk.
|
||||||
|
type DiskFreeSectors [50]TrackFreeSectors
|
||||||
|
|
||||||
// VTOC is the struct used to hold the DOS 3.3 VTOC structure.
|
// VTOC is the struct used to hold the DOS 3.3 VTOC structure.
|
||||||
// See page 4-2 of Beneath Apple DOS.
|
// See page 4-2 of Beneath Apple DOS.
|
||||||
type VTOC struct {
|
type VTOC struct {
|
||||||
|
DiskSector
|
||||||
Unused1 byte // Not used
|
Unused1 byte // Not used
|
||||||
CatalogTrack byte // Track number of first catalog sector
|
CatalogTrack byte // Track number of first catalog sector
|
||||||
CatalogSector byte // Sector number of first catalog sector
|
CatalogSector byte // Sector number of first catalog sector
|
||||||
|
@ -30,11 +89,42 @@ type VTOC struct {
|
||||||
NumTracks byte // Number of tracks per diskette (normally 35)
|
NumTracks byte // Number of tracks per diskette (normally 35)
|
||||||
NumSectors byte // Number of sectors per track (13 or 16)
|
NumSectors byte // Number of sectors per track (13 or 16)
|
||||||
BytesPerSector uint16 // Number of bytes per sector (LO/HI format)
|
BytesPerSector uint16 // Number of bytes per sector (LO/HI format)
|
||||||
FreeSectors [50]FreeSectorMap
|
FreeSectors DiskFreeSectors
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalBinary marshals the VTOC sector to bytes. Error is always nil.
|
// Validate checks a VTOC sector to make sure it looks normal.
|
||||||
func (v VTOC) MarshalBinary() (data []byte, err error) {
|
func (v *VTOC) Validate() error {
|
||||||
|
if v.Volume == 255 {
|
||||||
|
return fmt.Errorf("expected volume to be 0-254, but got 255")
|
||||||
|
}
|
||||||
|
if v.DOSRelease != 3 {
|
||||||
|
return fmt.Errorf("expected DOS release number to be 3; got %d", v.DOSRelease)
|
||||||
|
}
|
||||||
|
if v.TrackDirection != 1 && v.TrackDirection != -1 {
|
||||||
|
return fmt.Errorf("expected track direction to be 1 or -1; got %d", v.TrackDirection)
|
||||||
|
}
|
||||||
|
if v.NumTracks != 35 {
|
||||||
|
return fmt.Errorf("expected number of tracks to be 35; got %d", v.NumTracks)
|
||||||
|
}
|
||||||
|
if v.NumSectors != 13 && v.NumSectors != 16 {
|
||||||
|
return fmt.Errorf("expected number of sectors per track to be 13 or 16; got %d", v.NumSectors)
|
||||||
|
}
|
||||||
|
if v.BytesPerSector != 256 {
|
||||||
|
return fmt.Errorf("expected 256 bytes per sector; got %d", v.BytesPerSector)
|
||||||
|
}
|
||||||
|
if v.TrackSectorListMaxSize != 122 {
|
||||||
|
return fmt.Errorf("expected 122 track/sector pairs per track/sector list sector; got %d", v.TrackSectorListMaxSize)
|
||||||
|
}
|
||||||
|
for i, tf := range v.FreeSectors {
|
||||||
|
if !tf.UnusedClear() {
|
||||||
|
return fmt.Errorf("unused bytes of free-sector list for track %d are not zeroes", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSector marshals the VTOC sector to bytes.
|
||||||
|
func (v VTOC) ToSector() []byte {
|
||||||
buf := make([]byte, 256)
|
buf := make([]byte, 256)
|
||||||
buf[0x00] = v.Unused1
|
buf[0x00] = v.Unused1
|
||||||
buf[0x01] = v.CatalogTrack
|
buf[0x01] = v.CatalogTrack
|
||||||
|
@ -54,7 +144,7 @@ func (v VTOC) MarshalBinary() (data []byte, err error) {
|
||||||
for i, m := range v.FreeSectors {
|
for i, m := range v.FreeSectors {
|
||||||
copyBytes(buf[0x38+4*i:0x38+4*i+4], m[:])
|
copyBytes(buf[0x38+4*i:0x38+4*i+4], m[:])
|
||||||
}
|
}
|
||||||
return buf, nil
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyBytes is just like the builtin copy, but just for byte slices,
|
// copyBytes is just like the builtin copy, but just for byte slices,
|
||||||
|
@ -66,11 +156,11 @@ func copyBytes(dst, src []byte) int {
|
||||||
return copy(dst, src)
|
return copy(dst, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalBinary unmarshals the VTOC sector from bytes. Input is
|
// FromSector unmarshals the VTOC sector from bytes. Input is
|
||||||
// expected to be exactly 256 bytes.
|
// expected to be exactly 256 bytes.
|
||||||
func (v *VTOC) UnmarshalBinary(data []byte) error {
|
func (v *VTOC) FromSector(data []byte) {
|
||||||
if len(data) != 256 {
|
if len(data) != 256 {
|
||||||
return fmt.Errorf("VTOC.UnmarshalBinary expects exactly 256 bytes; got %d", len(data))
|
panic(fmt.Sprintf("VTOC.FromSector expects exactly 256 bytes; got %d", len(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Unused1 = data[0x00]
|
v.Unused1 = data[0x00]
|
||||||
|
@ -91,8 +181,6 @@ func (v *VTOC) UnmarshalBinary(data []byte) error {
|
||||||
for i := range v.FreeSectors {
|
for i := range v.FreeSectors {
|
||||||
copyBytes(v.FreeSectors[i][:], data[0x38+4*i:0x38+4*i+4])
|
copyBytes(v.FreeSectors[i][:], data[0x38+4*i:0x38+4*i+4])
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultVTOC() VTOC {
|
func DefaultVTOC() VTOC {
|
||||||
|
@ -109,9 +197,9 @@ func DefaultVTOC() VTOC {
|
||||||
BytesPerSector: 0x100,
|
BytesPerSector: 0x100,
|
||||||
}
|
}
|
||||||
for i := range v.FreeSectors {
|
for i := range v.FreeSectors {
|
||||||
v.FreeSectors[i] = FreeSectorMap{}
|
v.FreeSectors[i] = TrackFreeSectors{}
|
||||||
if i < 35 {
|
if i < 35 {
|
||||||
v.FreeSectors[i] = FreeSectorMap([4]byte{0xff, 0xff, 0x00, 0x00})
|
v.FreeSectors[i] = TrackFreeSectors([4]byte{0xff, 0xff, 0x00, 0x00})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
|
@ -120,6 +208,7 @@ func DefaultVTOC() VTOC {
|
||||||
// CatalogSector is the struct used to hold the DOS 3.3 Catalog
|
// CatalogSector is the struct used to hold the DOS 3.3 Catalog
|
||||||
// sector.
|
// sector.
|
||||||
type CatalogSector struct {
|
type CatalogSector struct {
|
||||||
|
DiskSector
|
||||||
Unused1 byte // Not used
|
Unused1 byte // Not used
|
||||||
NextTrack byte // Track number of next catalog sector (usually 11 hex)
|
NextTrack byte // Track number of next catalog sector (usually 11 hex)
|
||||||
NextSector byte // Sector number of next catalog sector
|
NextSector byte // Sector number of next catalog sector
|
||||||
|
@ -127,25 +216,25 @@ type CatalogSector struct {
|
||||||
FileDescs [7]FileDesc // File descriptive entries
|
FileDescs [7]FileDesc // File descriptive entries
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalBinary marshals the CatalogSector to bytes. Error is always nil.
|
// ToSector marshals the CatalogSector to bytes.
|
||||||
func (cs CatalogSector) MarshalBinary() (data []byte, err error) {
|
func (cs CatalogSector) ToSector() []byte {
|
||||||
buf := make([]byte, 256)
|
buf := make([]byte, 256)
|
||||||
buf[0x00] = cs.Unused1
|
buf[0x00] = cs.Unused1
|
||||||
buf[0x01] = cs.NextTrack
|
buf[0x01] = cs.NextTrack
|
||||||
buf[0x02] = cs.NextSector
|
buf[0x02] = cs.NextSector
|
||||||
copyBytes(buf[0x03:0x0b], cs.Unused2[:])
|
copyBytes(buf[0x03:0x0b], cs.Unused2[:])
|
||||||
for i, fd := range cs.FileDescs {
|
for i, fd := range cs.FileDescs {
|
||||||
fdBytes, _ := fd.MarshalBinary()
|
fdBytes := fd.ToBytes()
|
||||||
copyBytes(buf[0x0b+35*i:0x0b+35*(i+1)], fdBytes)
|
copyBytes(buf[0x0b+35*i:0x0b+35*(i+1)], fdBytes)
|
||||||
}
|
}
|
||||||
return buf, nil
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalBinary unmarshals the CatalogSector from bytes. Input is
|
// FromSector unmarshals the CatalogSector from bytes. Input is
|
||||||
// expected to be exactly 256 bytes.
|
// expected to be exactly 256 bytes.
|
||||||
func (cs *CatalogSector) UnmarshalBinary(data []byte) error {
|
func (cs *CatalogSector) FromSector(data []byte) {
|
||||||
if len(data) != 256 {
|
if len(data) != 256 {
|
||||||
return fmt.Errorf("CatalogSector.UnmarshalBinary expects exactly 256 bytes; got %d", len(data))
|
panic(fmt.Sprintf("CatalogSector.FromSector expects exactly 256 bytes; got %d", len(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.Unused1 = data[0x00]
|
cs.Unused1 = data[0x00]
|
||||||
|
@ -154,12 +243,8 @@ func (cs *CatalogSector) UnmarshalBinary(data []byte) error {
|
||||||
copyBytes(cs.Unused2[:], data[0x03:0x0b])
|
copyBytes(cs.Unused2[:], data[0x03:0x0b])
|
||||||
|
|
||||||
for i := range cs.FileDescs {
|
for i := range cs.FileDescs {
|
||||||
if err := cs.FileDescs[i].UnmarshalBinary(data[0x0b+35*i : 0x0b+35*(i+1)]); err != nil {
|
cs.FileDescs[i].FromBytes(data[0x0b+35*i : 0x0b+35*(i+1)])
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Filetype byte
|
type Filetype byte
|
||||||
|
@ -179,6 +264,14 @@ const (
|
||||||
FileTypeB Filetype = 0x40 // B type file
|
FileTypeB Filetype = 0x40 // B type file
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FileDescStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileDescStatusNormal FileDescStatus = iota
|
||||||
|
FileDescStatusDeleted
|
||||||
|
FileDescStatusUnused
|
||||||
|
)
|
||||||
|
|
||||||
// FileDesc is the struct used to represent the DOS 3.3 File
|
// FileDesc is the struct used to represent the DOS 3.3 File
|
||||||
// Descriptive entry.
|
// Descriptive entry.
|
||||||
type FileDesc struct {
|
type FileDesc struct {
|
||||||
|
@ -199,8 +292,8 @@ type FileDesc struct {
|
||||||
SectorCount uint16
|
SectorCount uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalBinary marshals the FileDesc to bytes. Error is always nil.
|
// ToBytes marshals the FileDesc to bytes.
|
||||||
func (fd FileDesc) MarshalBinary() (data []byte, err error) {
|
func (fd FileDesc) ToBytes() []byte {
|
||||||
buf := make([]byte, 35)
|
buf := make([]byte, 35)
|
||||||
buf[0x00] = fd.TrackSectorListTrack
|
buf[0x00] = fd.TrackSectorListTrack
|
||||||
buf[0x01] = fd.TrackSectorListSector
|
buf[0x01] = fd.TrackSectorListSector
|
||||||
|
@ -208,21 +301,158 @@ func (fd FileDesc) MarshalBinary() (data []byte, err error) {
|
||||||
copyBytes(buf[0x03:0x21], fd.Filename[:])
|
copyBytes(buf[0x03:0x21], fd.Filename[:])
|
||||||
binary.LittleEndian.PutUint16(buf[0x21:0x23], fd.SectorCount)
|
binary.LittleEndian.PutUint16(buf[0x21:0x23], fd.SectorCount)
|
||||||
|
|
||||||
return buf, nil
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalBinary unmarshals the FileDesc from bytes. Input is
|
// FromBytes unmarshals the FileDesc from bytes. Input is
|
||||||
// expected to be exactly 35 bytes.
|
// expected to be exactly 35 bytes.
|
||||||
func (fd *FileDesc) UnmarshalBinary(data []byte) error {
|
func (fd *FileDesc) FromBytes(data []byte) {
|
||||||
if len(data) != 35 {
|
if len(data) != 35 {
|
||||||
return fmt.Errorf("FileDesc.UnmarshalBinary expects exactly 35 bytes; got %d", len(data))
|
panic(fmt.Sprintf("FileDesc.FromBytes expects exactly 35 bytes; got %d", len(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fd.TrackSectorListTrack = data[0x00]
|
fd.TrackSectorListTrack = data[0x00]
|
||||||
fd.TrackSectorListSector = data[0x01]
|
fd.TrackSectorListSector = data[0x01]
|
||||||
fd.Filetype = Filetype(data[0x02])
|
fd.Filetype = Filetype(data[0x02])
|
||||||
copyBytes(data[0x03:0x21], fd.Filename[:])
|
copyBytes(fd.Filename[:], data[0x03:0x21])
|
||||||
fd.SectorCount = binary.LittleEndian.Uint16(data[0x21:0x23])
|
fd.SectorCount = binary.LittleEndian.Uint16(data[0x21:0x23])
|
||||||
|
}
|
||||||
return nil
|
|
||||||
|
// Status returns whether the FileDesc describes a deleted file, a
|
||||||
|
// normal file, or has never been used.
|
||||||
|
func (fd *FileDesc) Status() FileDescStatus {
|
||||||
|
switch fd.TrackSectorListTrack {
|
||||||
|
case 0:
|
||||||
|
return FileDescStatusUnused // Never been used.
|
||||||
|
case 0xff:
|
||||||
|
return FileDescStatusDeleted
|
||||||
|
default:
|
||||||
|
return FileDescStatusNormal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilenameString returns the filename of a FileDesc as a normal
|
||||||
|
// string.
|
||||||
|
func (fd *FileDesc) FilenameString() string {
|
||||||
|
var slice []byte
|
||||||
|
if fd.Status() == FileDescStatusDeleted {
|
||||||
|
slice = append(slice, fd.Filename[0:len(fd.Filename)-1]...)
|
||||||
|
} else {
|
||||||
|
slice = append(slice, fd.Filename[:]...)
|
||||||
|
}
|
||||||
|
for i := range slice {
|
||||||
|
slice[i] -= 0x80
|
||||||
|
}
|
||||||
|
return strings.TrimRight(string(slice), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackSector struct {
|
||||||
|
Track byte
|
||||||
|
Sector byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackSectorList is the struct used to represent DOS 3.3
|
||||||
|
// Track/Sector List sectors.
|
||||||
|
type TrackSectorList struct {
|
||||||
|
DiskSector
|
||||||
|
Unused1 byte // Not used
|
||||||
|
NextTrack byte // Track number of next T/S List sector if one was needed or zero if no more T/S List sectors.
|
||||||
|
NextSector byte // Sector number of next T/S List sector (if present).
|
||||||
|
Unused2 [2]byte // Not used
|
||||||
|
SectorOffset uint16 // Sector offset in file of the first sector described by this list.
|
||||||
|
Unused3 [5]byte // Not used
|
||||||
|
TrackSectors [122]TrackSector
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToSector marshals the TrackSectorList to bytes.
|
||||||
|
func (tsl TrackSectorList) ToSector() []byte {
|
||||||
|
buf := make([]byte, 256)
|
||||||
|
buf[0x00] = tsl.Unused1
|
||||||
|
buf[0x01] = tsl.NextTrack
|
||||||
|
buf[0x02] = tsl.NextSector
|
||||||
|
copyBytes(buf[0x03:0x05], tsl.Unused2[:])
|
||||||
|
binary.LittleEndian.PutUint16(buf[0x05:0x07], tsl.SectorOffset)
|
||||||
|
copyBytes(buf[0x07:0x0C], tsl.Unused3[:])
|
||||||
|
|
||||||
|
for i, ts := range tsl.TrackSectors {
|
||||||
|
buf[0x0C+i*2] = ts.Track
|
||||||
|
buf[0x0D+i*2] = ts.Sector
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromSector unmarshals the TrackSectorList from bytes. Input is
|
||||||
|
// expected to be exactly 256 bytes.
|
||||||
|
func (tsl *TrackSectorList) FromSector(data []byte) {
|
||||||
|
if len(data) != 256 {
|
||||||
|
panic(fmt.Sprintf("TrackSectorList.FromSector expects exactly 256 bytes; got %d", len(data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
tsl.Unused1 = data[0x00]
|
||||||
|
tsl.NextTrack = data[0x01]
|
||||||
|
tsl.NextSector = data[0x02]
|
||||||
|
copyBytes(tsl.Unused2[:], data[0x03:0x05])
|
||||||
|
tsl.SectorOffset = binary.LittleEndian.Uint16(data[0x05:0x07])
|
||||||
|
copyBytes(tsl.Unused3[:], data[0x07:0x0C])
|
||||||
|
|
||||||
|
for i := range tsl.TrackSectors {
|
||||||
|
tsl.TrackSectors[i].Track = data[0x0C+i*2]
|
||||||
|
tsl.TrackSectors[i].Sector = data[0x0D+i*2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readCatalogSectors reads the raw CatalogSector structs from a DOS
|
||||||
|
// 3.3 disk.
|
||||||
|
func readCatalogSectors(d disk.SectorDisk) ([]CatalogSector, error) {
|
||||||
|
v := &VTOC{}
|
||||||
|
err := disk.UnmarshalLogicalSector(d, v, VTOCTrack, VTOCSector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := v.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid VTOC sector: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTrack := v.CatalogTrack
|
||||||
|
nextSector := v.CatalogSector
|
||||||
|
css := []CatalogSector{}
|
||||||
|
for nextTrack != 0 || nextSector != 0 {
|
||||||
|
if nextTrack >= v.NumTracks {
|
||||||
|
return nil, fmt.Errorf("catalog sectors can't be in track %d: disk only has %d tracks", nextTrack, v.NumTracks)
|
||||||
|
}
|
||||||
|
if nextSector >= v.NumSectors {
|
||||||
|
return nil, fmt.Errorf("catalog sectors can't be in sector %d: disk only has %d sectors", nextSector, v.NumSectors)
|
||||||
|
}
|
||||||
|
cs := CatalogSector{}
|
||||||
|
err := disk.UnmarshalLogicalSector(d, &cs, nextTrack, nextSector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
css = append(css, cs)
|
||||||
|
nextTrack = cs.NextTrack
|
||||||
|
nextSector = cs.NextSector
|
||||||
|
}
|
||||||
|
return css, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadCatalog reads the catalog of a DOS 3.3 disk.
|
||||||
|
func ReadCatalog(d disk.SectorDisk) (files, deleted []FileDesc, err error) {
|
||||||
|
css, err := readCatalogSectors(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cs := range css {
|
||||||
|
for _, fd := range cs.FileDescs {
|
||||||
|
switch fd.Status() {
|
||||||
|
case FileDescStatusUnused:
|
||||||
|
// skip
|
||||||
|
case FileDescStatusDeleted:
|
||||||
|
deleted = append(deleted, fd)
|
||||||
|
case FileDescStatusNormal:
|
||||||
|
files = append(files, fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files, deleted, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,24 +4,24 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/zellyn/diskii/lib/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestVTOCMarshalRoundtrip checks a simple roundtrip of VTOC data.
|
// TestVTOCMarshalRoundtrip checks a simple roundtrip of VTOC data.
|
||||||
func TestVTOCMarshalRoundtrip(t *testing.T) {
|
func TestVTOCMarshalRoundtrip(t *testing.T) {
|
||||||
buf := make([]byte, 256)
|
buf := make([]byte, 256)
|
||||||
rand.Read(buf)
|
rand.Read(buf)
|
||||||
|
buf1 := make([]byte, 256)
|
||||||
|
copy(buf1, buf)
|
||||||
vtoc1 := &VTOC{}
|
vtoc1 := &VTOC{}
|
||||||
if err := vtoc1.UnmarshalBinary(buf); err != nil {
|
vtoc1.FromSector(buf1)
|
||||||
t.Fatal(err)
|
buf2 := vtoc1.ToSector()
|
||||||
}
|
|
||||||
buf2, _ := vtoc1.MarshalBinary()
|
|
||||||
if !reflect.DeepEqual(buf, buf2) {
|
if !reflect.DeepEqual(buf, buf2) {
|
||||||
t.Errorf("Buffers differ: %v != %v", buf, buf2)
|
t.Errorf("Buffers differ: %v != %v", buf, buf2)
|
||||||
}
|
}
|
||||||
vtoc2 := &VTOC{}
|
vtoc2 := &VTOC{}
|
||||||
if err := vtoc2.UnmarshalBinary(buf2); err != nil {
|
vtoc2.FromSector(buf2)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if *vtoc1 != *vtoc2 {
|
if *vtoc1 != *vtoc2 {
|
||||||
t.Errorf("Structs differ: %v != %v", vtoc1, vtoc2)
|
t.Errorf("Structs differ: %v != %v", vtoc1, vtoc2)
|
||||||
}
|
}
|
||||||
|
@ -31,19 +31,105 @@ func TestVTOCMarshalRoundtrip(t *testing.T) {
|
||||||
func TestCatalogSectorMarshalRoundtrip(t *testing.T) {
|
func TestCatalogSectorMarshalRoundtrip(t *testing.T) {
|
||||||
buf := make([]byte, 256)
|
buf := make([]byte, 256)
|
||||||
rand.Read(buf)
|
rand.Read(buf)
|
||||||
|
buf1 := make([]byte, 256)
|
||||||
|
copy(buf1, buf)
|
||||||
cs1 := &CatalogSector{}
|
cs1 := &CatalogSector{}
|
||||||
if err := cs1.UnmarshalBinary(buf); err != nil {
|
cs1.FromSector(buf1)
|
||||||
t.Fatal(err)
|
buf2 := cs1.ToSector()
|
||||||
}
|
|
||||||
buf2, _ := cs1.MarshalBinary()
|
|
||||||
if !reflect.DeepEqual(buf, buf2) {
|
if !reflect.DeepEqual(buf, buf2) {
|
||||||
t.Errorf("Buffers differ: %v != %v", buf, buf2)
|
t.Errorf("Buffers differ: %v != %v", buf, buf2)
|
||||||
}
|
}
|
||||||
cs2 := &CatalogSector{}
|
cs2 := &CatalogSector{}
|
||||||
if err := cs2.UnmarshalBinary(buf2); err != nil {
|
cs2.FromSector(buf2)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if *cs1 != *cs2 {
|
if *cs1 != *cs2 {
|
||||||
t.Errorf("Structs differ: %v != %v", cs1, cs2)
|
t.Errorf("Structs differ: %v != %v", cs1, cs2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTrackSectorListMarshalRoundtrip checks a simple roundtrip of TrackSectorList data.
|
||||||
|
func TestTrackSectorListMarshalRoundtrip(t *testing.T) {
|
||||||
|
buf := make([]byte, 256)
|
||||||
|
rand.Read(buf)
|
||||||
|
buf1 := make([]byte, 256)
|
||||||
|
copy(buf1, buf)
|
||||||
|
cs1 := &TrackSectorList{}
|
||||||
|
cs1.FromSector(buf1)
|
||||||
|
buf2 := cs1.ToSector()
|
||||||
|
if !reflect.DeepEqual(buf, buf2) {
|
||||||
|
t.Errorf("Buffers differ: %v != %v", buf, buf2)
|
||||||
|
}
|
||||||
|
cs2 := &TrackSectorList{}
|
||||||
|
cs2.FromSector(buf2)
|
||||||
|
if *cs1 != *cs2 {
|
||||||
|
t.Errorf("Structs differ: %v != %v", cs1, cs2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadCatalog tests the reading of the catalog of a test disk.
|
||||||
|
func TestReadCatalog(t *testing.T) {
|
||||||
|
dsk, err := disk.LoadDSK("testdata/dos33test.dsk")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fds, deleted, err := ReadCatalog(dsk)
|
||||||
|
|
||||||
|
fdsWant := []struct {
|
||||||
|
locked bool
|
||||||
|
typ string
|
||||||
|
size int
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{true, "A", 3, "HELLO"},
|
||||||
|
{true, "I", 3, "APPLESOFT"},
|
||||||
|
{true, "B", 6, "LOADER.OBJ0"},
|
||||||
|
{true, "B", 42, "FPBASIC"},
|
||||||
|
{true, "B", 42, "INTBASIC"},
|
||||||
|
{true, "A", 3, "MASTER"},
|
||||||
|
{true, "B", 9, "MASTER CREATE"},
|
||||||
|
{true, "I", 9, "COPY"},
|
||||||
|
{true, "B", 3, "COPY.OBJ0"},
|
||||||
|
{true, "A", 9, "COPYA"},
|
||||||
|
{true, "B", 3, "CHAIN"},
|
||||||
|
{true, "A", 14, "RENUMBER"},
|
||||||
|
{true, "A", 3, "FILEM"},
|
||||||
|
{true, "B", 20, "FID"},
|
||||||
|
{true, "A", 3, "CONVERT13"},
|
||||||
|
{true, "B", 27, "MUFFIN"},
|
||||||
|
{true, "A", 3, "START13"},
|
||||||
|
{true, "B", 7, "BOOT13"},
|
||||||
|
{true, "A", 4, "SLOT#"},
|
||||||
|
{false, "A", 3, "EXAMPLE"},
|
||||||
|
}
|
||||||
|
|
||||||
|
deletedWant := []struct {
|
||||||
|
locked bool
|
||||||
|
typ string
|
||||||
|
size int
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{false, "A", 3, "EXAMPLE2"},
|
||||||
|
{false, "I", 3, "EXAMPLE3"},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fdsWant) != len(fds) {
|
||||||
|
t.Fatalf("Want %d undeleted files; got %d", len(fdsWant), len(fds))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deletedWant) != len(deleted) {
|
||||||
|
t.Fatalf("Want %d deleted files; got %d", len(deletedWant), len(deleted))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, wantInfo := range fdsWant {
|
||||||
|
if want, got := wantInfo.name, fds[i].FilenameString(); want != got {
|
||||||
|
t.Errorf("Want filename %d to be %q; got %q", i+1, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, wantInfo := range deletedWant {
|
||||||
|
if want, got := wantInfo.name, deleted[i].FilenameString(); want != got {
|
||||||
|
t.Errorf("Want deleted filename %d to be %q; got %q", i+1, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(zellyn): Check type, size, locked status.
|
||||||
|
}
|
||||||
|
|
BIN
lib/dos33/testdata/dos33test.dsk
vendored
Normal file
BIN
lib/dos33/testdata/dos33test.dsk
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user