-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
277 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
// Copyright © 2016 Zellyn Hunter <[email protected]> | ||
|
||
// Package dos33 contains routines for working with the on-disk structures of DOS 3.3. | ||
package dos33 | ||
|
||
import ( | ||
"encoding/binary" | ||
"fmt" | ||
) | ||
|
||
type FreeSectorMap [4]byte // Bit map of free sectors in a track | ||
|
||
// VTOC is the struct used to hold the DOS 3.3 VTOC structure. | ||
// See page 4-2 of Beneath Apple DOS. | ||
type VTOC struct { | ||
Unused1 byte // Not used | ||
CatalogTrack byte // Track number of first catalog sector | ||
CatalogSector byte // Sector number of first catalog sector | ||
DOSRelease byte // Release number of DOS used to INIT this diskette | ||
Unused2 [2]byte // Not used | ||
Volume byte // Diskette volume number (1-254) | ||
Unused3 [32]byte // Not used | ||
// Maximum number of track/secotr pairs which will fit in one file | ||
// track/sector list sector (122 for 256 byte sectors) | ||
TrackSectorListMaxSize byte | ||
Unused4 [8]byte // Not used | ||
LastTrack byte // Last track where sectors were allocated | ||
TrackDirection int8 // Direction of track allocation (+1 or -1) | ||
Unused5 [2]byte | ||
NumTracks byte // Number of tracks per diskette (normally 35) | ||
NumSectors byte // Number of sectors per track (13 or 16) | ||
BytesPerSector uint16 // Number of bytes per sector (LO/HI format) | ||
FreeSectors [50]FreeSectorMap | ||
} | ||
|
||
// MarshalBinary marshals the VTOC sector to bytes. Error is always nil. | ||
func (v VTOC) MarshalBinary() (data []byte, err error) { | ||
buf := make([]byte, 256) | ||
buf[0x00] = v.Unused1 | ||
buf[0x01] = v.CatalogTrack | ||
buf[0x02] = v.CatalogSector | ||
buf[0x03] = v.DOSRelease | ||
copyBytes(buf[0x04:0x06], v.Unused2[:]) | ||
buf[0x06] = v.Volume | ||
copyBytes(buf[0x07:0x27], v.Unused3[:]) | ||
buf[0x27] = v.TrackSectorListMaxSize | ||
copyBytes(buf[0x28:0x30], v.Unused4[:]) | ||
buf[0x30] = v.LastTrack | ||
buf[0x31] = byte(v.TrackDirection) | ||
copyBytes(buf[0x32:0x34], v.Unused5[:]) | ||
buf[0x34] = v.NumTracks | ||
buf[0x35] = v.NumSectors | ||
binary.LittleEndian.PutUint16(buf[0x36:0x38], v.BytesPerSector) | ||
for i, m := range v.FreeSectors { | ||
copyBytes(buf[0x38+4*i:0x38+4*i+4], m[:]) | ||
} | ||
return buf, nil | ||
} | ||
|
||
// copyBytes is just like the builtin copy, but just for byte slices, | ||
// and it checks that dst and src have the same length. | ||
func copyBytes(dst, src []byte) int { | ||
if len(dst) != len(src) { | ||
panic(fmt.Sprintf("copyBytes called with differing lengths %d and %d", len(dst), len(src))) | ||
} | ||
return copy(dst, src) | ||
} | ||
|
||
// UnmarshalBinary unmarshals the VTOC sector from bytes. Input is | ||
// expected to be exactly 256 bytes. | ||
func (v *VTOC) UnmarshalBinary(data []byte) error { | ||
if len(data) != 256 { | ||
return fmt.Errorf("VTOC.UnmarshalBinary expects exactly 256 bytes; got %d", len(data)) | ||
} | ||
|
||
v.Unused1 = data[0x00] | ||
v.CatalogTrack = data[0x01] | ||
v.CatalogSector = data[0x02] | ||
v.DOSRelease = data[0x03] | ||
copyBytes(v.Unused2[:], data[0x04:0x06]) | ||
v.Volume = data[0x06] | ||
copyBytes(v.Unused3[:], data[0x07:0x27]) | ||
v.TrackSectorListMaxSize = data[0x27] | ||
copyBytes(v.Unused4[:], data[0x28:0x30]) | ||
v.LastTrack = data[0x30] | ||
v.TrackDirection = int8(data[0x31]) | ||
copyBytes(v.Unused5[:], data[0x32:0x34]) | ||
v.NumTracks = data[0x34] | ||
v.NumSectors = data[0x35] | ||
v.BytesPerSector = binary.LittleEndian.Uint16(data[0x36:0x38]) | ||
for i := range v.FreeSectors { | ||
copyBytes(v.FreeSectors[i][:], data[0x38+4*i:0x38+4*i+4]) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func DefaultVTOC() VTOC { | ||
v := VTOC{ | ||
CatalogTrack: 0x11, | ||
CatalogSector: 0x0f, | ||
DOSRelease: 0x03, | ||
Volume: 0x01, | ||
TrackSectorListMaxSize: 122, | ||
LastTrack: 0x00, // TODO(zellyn): what should this be? | ||
TrackDirection: 1, | ||
NumTracks: 0x23, | ||
NumSectors: 0x10, | ||
BytesPerSector: 0x100, | ||
} | ||
for i := range v.FreeSectors { | ||
v.FreeSectors[i] = FreeSectorMap{} | ||
if i < 35 { | ||
v.FreeSectors[i] = FreeSectorMap([4]byte{0xff, 0xff, 0x00, 0x00}) | ||
} | ||
} | ||
return v | ||
} | ||
|
||
// CatalogSector is the struct used to hold the DOS 3.3 Catalog | ||
// sector. | ||
type CatalogSector struct { | ||
Unused1 byte // Not used | ||
NextTrack byte // Track number of next catalog sector (usually 11 hex) | ||
NextSector byte // Sector number of next catalog sector | ||
Unused2 [8]byte // Not used | ||
FileDescs [7]FileDesc // File descriptive entries | ||
} | ||
|
||
// MarshalBinary marshals the CatalogSector to bytes. Error is always nil. | ||
func (cs CatalogSector) MarshalBinary() (data []byte, err error) { | ||
buf := make([]byte, 256) | ||
buf[0x00] = cs.Unused1 | ||
buf[0x01] = cs.NextTrack | ||
buf[0x02] = cs.NextSector | ||
copyBytes(buf[0x03:0x0b], cs.Unused2[:]) | ||
for i, fd := range cs.FileDescs { | ||
fdBytes, _ := fd.MarshalBinary() | ||
copyBytes(buf[0x0b+35*i:0x0b+35*(i+1)], fdBytes) | ||
} | ||
return buf, nil | ||
} | ||
|
||
// UnmarshalBinary unmarshals the CatalogSector from bytes. Input is | ||
// expected to be exactly 256 bytes. | ||
func (cs *CatalogSector) UnmarshalBinary(data []byte) error { | ||
if len(data) != 256 { | ||
return fmt.Errorf("CatalogSector.UnmarshalBinary expects exactly 256 bytes; got %d", len(data)) | ||
} | ||
|
||
cs.Unused1 = data[0x00] | ||
cs.NextTrack = data[0x01] | ||
cs.NextSector = data[0x02] | ||
copyBytes(cs.Unused2[:], data[0x03:0x0b]) | ||
|
||
for i := range cs.FileDescs { | ||
if err := cs.FileDescs[i].UnmarshalBinary(data[0x0b+35*i : 0x0b+35*(i+1)]); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type Filetype byte | ||
|
||
const ( | ||
// Hex 80+file type - file is locked | ||
// Hex 00+file type - file is not locked | ||
FiletypeLocked Filetype = 0x80 | ||
|
||
FileTypeText Filetype = 0x00 // Text file | ||
FileTypeInteger Filetype = 0x01 // INTEGER BASIC file | ||
FileTypeApplesoft Filetype = 0x02 // APPLESOFT BASIC file | ||
FileTypeBinary Filetype = 0x04 // BINARY file | ||
FileTypeS Filetype = 0x08 // S type file | ||
FileTypeRelocatable Filetype = 0x10 // RELOCATABLE object module file | ||
FileTypeA Filetype = 0x20 // A type file | ||
FileTypeB Filetype = 0x40 // B type file | ||
) | ||
|
||
// FileDesc is the struct used to represent the DOS 3.3 File | ||
// Descriptive entry. | ||
type FileDesc struct { | ||
// Track of first track/sector list sector. If this is a deleted | ||
// file, this byte contains a hex FF and the original track number | ||
// is copied to the last byte of the file name field (BYTE 20). If | ||
// this byte contains a hex 00, the entry is assumed to never have | ||
// been used and is available for use. (This means track 0 can never | ||
// be used for data even if the DOS image is "wiped" from the | ||
// diskette.) | ||
TrackSectorListTrack byte | ||
TrackSectorListSector byte // Sector of first track/sector list sector | ||
Filetype Filetype // File type and flags | ||
Filename [30]byte // File name (30 characters) Length of file in | ||
// sectors (LO/HI format). The CATALOG command will only format the | ||
// LO byte of this length giving 1-255 but a full 65,535 may be | ||
// stored here. | ||
SectorCount uint16 | ||
} | ||
|
||
// MarshalBinary marshals the FileDesc to bytes. Error is always nil. | ||
func (fd FileDesc) MarshalBinary() (data []byte, err error) { | ||
buf := make([]byte, 35) | ||
buf[0x00] = fd.TrackSectorListTrack | ||
buf[0x01] = fd.TrackSectorListSector | ||
buf[0x02] = byte(fd.Filetype) | ||
copyBytes(buf[0x03:0x21], fd.Filename[:]) | ||
binary.LittleEndian.PutUint16(buf[0x21:0x23], fd.SectorCount) | ||
|
||
return buf, nil | ||
} | ||
|
||
// UnmarshalBinary unmarshals the FileDesc from bytes. Input is | ||
// expected to be exactly 35 bytes. | ||
func (fd *FileDesc) UnmarshalBinary(data []byte) error { | ||
if len(data) != 35 { | ||
return fmt.Errorf("FileDesc.UnmarshalBinary expects exactly 35 bytes; got %d", len(data)) | ||
} | ||
|
||
fd.TrackSectorListTrack = data[0x00] | ||
fd.TrackSectorListSector = data[0x01] | ||
fd.Filetype = Filetype(data[0x02]) | ||
copyBytes(data[0x03:0x21], fd.Filename[:]) | ||
fd.SectorCount = binary.LittleEndian.Uint16(data[0x21:0x23]) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package dos33 | ||
|
||
import ( | ||
"crypto/rand" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
// TestVTOCMarshalRoundtrip checks a simple roundtrip of VTOC data. | ||
func TestVTOCMarshalRoundtrip(t *testing.T) { | ||
buf := make([]byte, 256) | ||
rand.Read(buf) | ||
vtoc1 := &VTOC{} | ||
if err := vtoc1.UnmarshalBinary(buf); err != nil { | ||
t.Fatal(err) | ||
} | ||
buf2, _ := vtoc1.MarshalBinary() | ||
if !reflect.DeepEqual(buf, buf2) { | ||
t.Errorf("Buffers differ: %v != %v", buf, buf2) | ||
} | ||
vtoc2 := &VTOC{} | ||
if err := vtoc2.UnmarshalBinary(buf2); err != nil { | ||
t.Fatal(err) | ||
} | ||
if *vtoc1 != *vtoc2 { | ||
t.Errorf("Structs differ: %v != %v", vtoc1, vtoc2) | ||
} | ||
} | ||
|
||
// TestCatalogSectorMarshalRoundtrip checks a simple roundtrip of CatalogSector data. | ||
func TestCatalogSectorMarshalRoundtrip(t *testing.T) { | ||
buf := make([]byte, 256) | ||
rand.Read(buf) | ||
cs1 := &CatalogSector{} | ||
if err := cs1.UnmarshalBinary(buf); err != nil { | ||
t.Fatal(err) | ||
} | ||
buf2, _ := cs1.MarshalBinary() | ||
if !reflect.DeepEqual(buf, buf2) { | ||
t.Errorf("Buffers differ: %v != %v", buf, buf2) | ||
} | ||
cs2 := &CatalogSector{} | ||
if err := cs2.UnmarshalBinary(buf2); err != nil { | ||
t.Fatal(err) | ||
} | ||
if *cs1 != *cs2 { | ||
t.Errorf("Structs differ: %v != %v", cs1, cs2) | ||
} | ||
} |