diff --git a/.golangci.yml b/.golangci.yml index e2601bb..ce4fe65 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,5 @@ +issues: + exclude-use-default: false linters: enable: - gocritic diff --git a/cmd/applesoft.go b/cmd/applesoft.go index 5cfb25c..ce79470 100644 --- a/cmd/applesoft.go +++ b/cmd/applesoft.go @@ -11,10 +11,12 @@ import ( "github.com/zellyn/diskii/types" ) +// ApplesoftCmd is the kong `applesoft` command. type ApplesoftCmd struct { Decode DecodeCmd `kong:"cmd,help='Convert a binary Applesoft program to a text LISTing.'"` } +// DecodeCmd is the kong `decode` command. type DecodeCmd struct { Filename string `kong:"arg,default='-',type='existingfile',help='Binary Applesoft file to read, or “-” for stdin.'"` @@ -22,12 +24,14 @@ type DecodeCmd struct { Raw bool `kong:"short='r',help='Print raw control codes (no escaping)'"` } +// Help displays extended help and examples. func (d DecodeCmd) Help() string { return `Examples: # Dump the contents of HELLO and then decode it. diskii dump dos33master.dsk HELLO | diskii applesoft decode -` } +// Run the decode command. func (d *DecodeCmd) Run(globals *types.Globals) error { contents, err := helpers.FileContentsOrStdIn(d.Filename) if err != nil { @@ -38,9 +42,9 @@ func (d *DecodeCmd) Run(globals *types.Globals) error { return err } if d.Raw { - os.Stdout.WriteString(listing.String()) + _, _ = os.Stdout.WriteString(listing.String()) } else { - os.Stdout.WriteString(basic.ChevronControlCodes(listing.String())) + _, _ = os.Stdout.WriteString(basic.ChevronControlCodes(listing.String())) } return nil } diff --git a/cmd/catalog.go b/cmd/catalog.go index 947c4d1..dd97600 100644 --- a/cmd/catalog.go +++ b/cmd/catalog.go @@ -10,6 +10,7 @@ import ( "github.com/zellyn/diskii/types" ) +// LsCmd is the kong `ls` command. type LsCmd struct { Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"` System string `kong:"default='auto',enum='auto,dos3,prodos,nakedos',help='DOS system used for image.'"` @@ -19,6 +20,7 @@ type LsCmd struct { Directory string `kong:"arg,optional,help='Directory to list (ProDOS only).'"` } +// Help displays extended help and examples. func (l LsCmd) Help() string { return `Examples: # Simple ls of a disk image @@ -27,6 +29,7 @@ func (l LsCmd) Help() string { diskii ls --order do --system nakedos Super-Mon-2.0.dsk` } +// Run the `ls` command. func (l *LsCmd) Run(globals *types.Globals) error { op, order, err := disk.OpenFile(l.Image, l.Order, l.System, globals.DiskOperatorFactories, globals.Debug) if err != nil { diff --git a/cmd/delete.go b/cmd/delete.go index 3ad470e..2d4a7f2 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -9,6 +9,7 @@ import ( "github.com/zellyn/diskii/types" ) +// DeleteCmd is the kong `delete` command. type DeleteCmd struct { Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"` System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"` @@ -18,12 +19,14 @@ type DeleteCmd struct { Filename string `kong:"arg,required,help='Filename to use on disk.'"` } +// Help displays extended help and examples. func (d DeleteCmd) Help() string { return `Examples: # Delete file GREMLINS on disk image games.dsk. diskii rm games.dsk GREMLINS` } +// Run the `delete` command. func (d *DeleteCmd) Run(globals *types.Globals) error { op, order, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug) if err != nil { diff --git a/cmd/doc.go b/cmd/doc.go new file mode 100644 index 0000000..7812568 --- /dev/null +++ b/cmd/doc.go @@ -0,0 +1,2 @@ +// Package cmd contains the actual command implementations. +package cmd diff --git a/cmd/dump.go b/cmd/dump.go index d53884e..154ea20 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -9,6 +9,7 @@ import ( "github.com/zellyn/diskii/types" ) +// DumpCmd is the kong `dump` command. type DumpCmd struct { Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"` System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"` @@ -17,12 +18,14 @@ type DumpCmd struct { Filename string `kong:"arg,required,help='Filename to use on disk.'"` } +// Help displays extended help and examples. func (d DumpCmd) Help() string { return `Examples: # Dump file GREMLINS on disk image games.dsk. diskii dump games.dsk GREMLINS` } +// Run the `dump` command. func (d *DumpCmd) Run(globals *types.Globals) error { op, _, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug) if err != nil { diff --git a/cmd/filetypes.go b/cmd/filetypes.go index cbcc469..c39b03e 100644 --- a/cmd/filetypes.go +++ b/cmd/filetypes.go @@ -10,10 +10,12 @@ import ( "github.com/zellyn/diskii/types" ) +// FiletypesCmd is the kong `filetypes` command. type FiletypesCmd struct { All bool `kong:"help='Display all types, including SOS types and reserved ranges.'"` } +// Run the `filetypes` command. func (f *FiletypesCmd) Run(globals *types.Globals) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) fmt.Fprintln(w, "Description\tName\tThree-letter Name\tOne-letter Name") @@ -21,6 +23,6 @@ func (f *FiletypesCmd) Run(globals *types.Globals) error { for _, typ := range types.FiletypeInfos(f.All) { fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", typ.Desc, typ.Name, typ.ThreeLetter, typ.OneLetter) } - w.Flush() + _ = w.Flush() return nil } diff --git a/cmd/nakedos.go b/cmd/nakedos.go index bf6e8a6..aa08baa 100644 --- a/cmd/nakedos.go +++ b/cmd/nakedos.go @@ -12,6 +12,7 @@ import ( const helloName = "FHELLO" // filename to use (if Super-Mon) +// NakedOSCmd is the kong `nakedos` sub-command. type NakedOSCmd struct { Mkhello MkHelloCmd `kong:"cmd,help='Create an FHELLO program that loads and runs another file.'"` } @@ -23,6 +24,7 @@ func (n NakedOSCmd) Help() string { Presentation: https://www.kansasfest.org/2012/08/2010-haye-nakedos/` } +// MkHelloCmd is the kong `mkhello` command. type MkHelloCmd struct { Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"` @@ -33,6 +35,7 @@ type MkHelloCmd struct { Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"` } +// Help displays extended help and examples. func (m MkHelloCmd) Help() string { return `This command creates a very short DF01:FHELLO program that simply loads another program of your choice. @@ -44,6 +47,7 @@ Examples: mkhello test.dsk --address 0x2000 --start 0x2100 DF06` } +// Run the `mkhello` command. func (m *MkHelloCmd) Run(globals *types.Globals) error { if m.Start == 0xFFFF { m.Start = m.Address diff --git a/cmd/put.go b/cmd/put.go index 9143911..a1f2912 100644 --- a/cmd/put.go +++ b/cmd/put.go @@ -10,6 +10,7 @@ import ( "github.com/zellyn/diskii/types" ) +// PutCmd is the kong `put` command. type PutCmd struct { Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"` System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"` @@ -21,12 +22,14 @@ type PutCmd struct { SourceFilename string `kong:"arg,required,type='existingfile',help='Name of file containing data to put.'"` } +// Help displays extended help and examples. func (p PutCmd) Help() string { return `Examples: # Put file gremlins.o onto disk image games.dsk, using the filename GREMLINS. diskii put games.dsk GREMLINS gremlins.o` } +// Run the `put` command. func (p *PutCmd) Run(globals *types.Globals) error { if p.DiskImage == "-" { if p.SourceFilename == "-" { diff --git a/cmd/reorder.go b/cmd/reorder.go index 98c00f3..fe0e70b 100644 --- a/cmd/reorder.go +++ b/cmd/reorder.go @@ -8,6 +8,7 @@ import ( "github.com/zellyn/diskii/types" ) +// ReorderCmd is the kong `reorder` command. type ReorderCmd struct { Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"` NewOrder types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='New Logical-to-physical sector order.'"` @@ -17,6 +18,7 @@ type ReorderCmd struct { NewDiskImage string `kong:"arg,optional,type='path',help='Disk image to write, if different.'"` } +// Run the `reorder` command. func (r *ReorderCmd) Run(globals *types.Globals) error { fromOrderName, toOrderName, err := getOrders(r.DiskImage, r.Order, r.NewDiskImage, r.NewOrder) if err != nil { diff --git a/cmd/sd.go b/cmd/sd.go index 126890d..6edca56 100644 --- a/cmd/sd.go +++ b/cmd/sd.go @@ -12,6 +12,7 @@ import ( "github.com/zellyn/diskii/types" ) +// SDCmd is the kong `mksd` command. type SDCmd struct { Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"` @@ -22,6 +23,7 @@ type SDCmd struct { Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"` } +// Help displays extended help and examples. func (s SDCmd) Help() string { return ` See https://github.com/peterferrie/standard-delivery for details. @@ -34,6 +36,7 @@ Examples: diskii mksd test.dsk foo.o --address 0x2000 --start 0x2100` } +// Run the `mksd` command. func (s *SDCmd) Run(globals *types.Globals) error { if s.Start == 0xFFFF { s.Start = s.Address diff --git a/data/data.go b/data/data.go index 69d8268..a32144a 100644 --- a/data/data.go +++ b/data/data.go @@ -1,3 +1,4 @@ +// Package data is a bunch of go:embed embedded files. package data import _ "embed" diff --git a/disk/dev.go b/disk/dev.go deleted file mode 100644 index 4c3f7c0..0000000 --- a/disk/dev.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright © 2017 Zellyn Hunter - -// dev.go contains logic for reading ".po" disk images. - -package disk - -import ( - "fmt" - "io" - "io/ioutil" -) - -// A ProDOS block. -type Block [512]byte - -// Dev represents a .po disk image. -type Dev struct { - data []byte // The actual data in the file - blocks uint16 // Number of blocks -} - -// 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 -} - -// Order returns the order of blocks on the device. -func (d Dev) Order() string { - return "prodos" -} - -// 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/disk/disk.go b/disk/disk.go index 2b5f338..c26e57c 100644 --- a/disk/disk.go +++ b/disk/disk.go @@ -65,3 +65,6 @@ type TrackSector struct { Track byte Sector byte } + +// Block is a ProDOS block: 512 bytes. +type Block [512]byte diff --git a/disk/open.go b/disk/open.go index 4393203..7b9b4e2 100644 --- a/disk/open.go +++ b/disk/open.go @@ -5,6 +5,7 @@ import ( "io" "os" "path" + "path/filepath" "strings" "github.com/zellyn/diskii/helpers" @@ -16,15 +17,15 @@ func OpenFilename(filename string, order types.DiskOrder, system string, operato if filename == "-" { return OpenFile(os.Stdin, order, system, operatorFactories, debug) } - file, err := os.Open(filename) + file, err := os.Open(filepath.Clean(filename)) if err != nil { return nil, "", err } return OpenFile(file, order, system, operatorFactories, debug) } -// OpenImage attempts to open a disk or device image, using the provided ordering and system type. -// OpenImage will close the file. +// OpenFile attempts to open a disk or device image, using the provided ordering and system type. +// OpenFile will close the file. func OpenFile(file *os.File, order types.DiskOrder, system string, operatorFactories []types.OperatorFactory, debug bool) (types.Operator, types.DiskOrder, error) { bb, err := io.ReadAll(file) if err != nil { @@ -173,17 +174,6 @@ func Swizzle(diskimage []byte, order []int) ([]byte, error) { return result, nil } -func UnSwizzle(diskimage []byte, order []int) ([]byte, error) { - if err := validateOrder(order); err != nil { - return nil, fmt.Errorf("called UnSwizzle with weird order: %w", err) - } - reverseOrder := make([]int, FloppySectors) - for index, mapping := range order { - reverseOrder[mapping] = index - } - return Swizzle(diskimage, reverseOrder) -} - // validateOrder validates that an order mapping is valid, and maps [0,15] onto // [0,15] without repeats. func validateOrder(order []int) error { diff --git a/helpers/helpers.go b/helpers/helpers.go index b487b4f..5f173fb 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -21,6 +21,7 @@ func FileContentsOrStdIn(s string) ([]byte, error) { return os.ReadFile(s) } +// WriteOutput writes a byte slice to the given filename, using `-` for standard out. func WriteOutput(filename string, contents []byte, force bool) error { if filename == "-" { _, err := os.Stdout.Write(contents) diff --git a/prodos/prodos.go b/prodos/prodos.go index 9ff5f0e..dc3f49e 100644 --- a/prodos/prodos.go +++ b/prodos/prodos.go @@ -60,8 +60,10 @@ func (bp bitmapPart) ToBlock() (disk.Block, error) { return bp.data, nil } +// VolumeBitMap represents a volume bitmap. type VolumeBitMap []bitmapPart +// NewVolumeBitMap returns a volume bitmap of the given size. func NewVolumeBitMap(startBlock uint16, blocks uint16) VolumeBitMap { vbm := VolumeBitMap(make([]bitmapPart, (blocks+(512*8)-1)/(512*8))) for i := range vbm { @@ -73,10 +75,12 @@ func NewVolumeBitMap(startBlock uint16, blocks uint16) VolumeBitMap { return vbm } +// MarkUsed marks the given block as used. func (vbm VolumeBitMap) MarkUsed(block uint16) { vbm.mark(block, false) } +// MarkUnused marks the given block as free. func (vbm VolumeBitMap) MarkUnused(block uint16) { vbm.mark(block, true) } @@ -261,6 +265,7 @@ func (vdb VolumeDirectoryBlock) Validate() (errors []error) { return errors } +// VolumeDirectoryHeader represents a volume directory header. 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) @@ -319,14 +324,20 @@ func (vdh VolumeDirectoryHeader) Validate() (errors []error) { return errors } +// Access represents a level of file access. type Access byte const ( - AccessReadable Access = 0x01 - AccessWritable Access = 0x02 + // AccessReadable denotes a file as readable. + AccessReadable Access = 0x01 + // AccessWritable denotes a file as writable. + AccessWritable Access = 0x02 + // AccessChangedSinceBackup is (I think) always true on real disks. AccessChangedSinceBackup Access = 0x20 - AccessRenamable Access = 0x40 - AccessDestroyable Access = 0x80 + // AccessRenamable denotes a file as renamable. + AccessRenamable Access = 0x40 + // AccessDestroyable denotes a file as deletable. + AccessDestroyable Access = 0x80 ) // FileDescriptor is the entry in the volume directory for a file or @@ -420,7 +431,7 @@ func (fd FileDescriptor) Validate() (errors []error) { return errors } -// An index block contains 256 16-bit block numbers, pointing to other +// IndexBlock is an index block, containing 256 16-bit block numbers, pointing to other // blocks. The LSBs are stored in the first half, MSBs in the second. type IndexBlock disk.Block @@ -537,6 +548,7 @@ func (sb SubdirectoryBlock) Validate() (errors []error) { return errors } +// SubdirectoryHeader represents a subdirectory header. type SubdirectoryHeader struct { TypeAndNameLength byte // Storage type (top four bits) and subdirectory name length (lower four). SubdirectoryName [15]byte // Subdirectory name (actual length defined in TypeAndNameLength) diff --git a/supermon/supermon_test.go b/supermon/supermon_test.go index 3fdffb8..e6863b4 100644 --- a/supermon/supermon_test.go +++ b/supermon/supermon_test.go @@ -4,6 +4,7 @@ package supermon import ( "os" + "path/filepath" "reflect" "strings" "testing" @@ -48,7 +49,7 @@ func loadSectorMap(filename string) (SectorMap, []byte, error) { // getOperator gets a types.Operator for the given NakedOS disk, assumed to be // in "do" order. func getOperator(filename string) (types.Operator, error) { - f, err := os.Open(filename) + f, err := os.Open(filepath.Clean(filename)) if err != nil { return nil, err } diff --git a/types/ops.go b/types/ops.go index 7af9d4d..d9cb2d0 100644 --- a/types/ops.go +++ b/types/ops.go @@ -17,13 +17,19 @@ type Descriptor struct { Type Filetype } +// DiskOrder specifies the logical disk ordering. type DiskOrder string const ( - DiskOrderDO = DiskOrder("do") - DiskOrderPO = DiskOrder("po") - DiskOrderRaw = DiskOrder("raw") - DiskOrderAuto = DiskOrder("auto") + // DiskOrderDO is the DOS 3.3 logical ordering. + DiskOrderDO = DiskOrder("do") + // DiskOrderPO is the ProDOS logical ordering. + DiskOrderPO = DiskOrder("po") + // DiskOrderRaw is the logical ordering that doesn't change anything. + DiskOrderRaw = DiskOrder("raw") + // DiskOrderAuto is the logical ordering that tells diskii to guess. + DiskOrderAuto = DiskOrder("auto") + // DiskOrderUnknown is usually an error condition, or a signal that guessing failed. DiskOrderUnknown = DiskOrder("") ) diff --git a/types/types.go b/types/types.go index ddf1a8f..a06baf5 100644 --- a/types/types.go +++ b/types/types.go @@ -1,3 +1,5 @@ +// Package types holds various types that are needed all over the place. They're +// in their own package to avoid circular dependencies. package types // Globals holds flags and configuration that are shared globally. diff --git a/woz/woz.go b/woz/woz.go deleted file mode 100644 index cd08f0d..0000000 --- a/woz/woz.go +++ /dev/null @@ -1,268 +0,0 @@ -package woz - -import ( - "encoding/binary" - "fmt" - "hash" - "hash/crc32" - "io" - "strings" -) - -const wozHeader = "WOZ1\xFF\n\r\n" -const TrackLength = 6656 - -type Woz struct { - Info Info - Unknowns []UnknownChunk - TMap [160]uint8 - TRKS []TRK - Metadata Metadata -} - -type UnknownChunk struct { - ID string - Data []byte -} - -type DiskType uint8 - -const ( - DiskType525 DiskType = 1 - DiskType35 DiskType = 2 -) - -type Info struct { - Version uint8 - DiskType DiskType - WriteProtected bool - Synchronized bool - Cleaned bool - Creator string -} - -type TRK struct { - BitStream [6646]uint8 - BytesUsed uint16 - BitCount uint16 - SplicePoint uint16 - SpliceNibble uint8 - SpliceBitCount uint8 - Reserved uint16 -} - -type Metadata struct { - Keys []string - RawValues map[string]string -} - -type decoder struct { - r io.Reader - woz *Woz - crc hash.Hash32 - tmp [3 * 256]byte - crcVal uint32 -} - -// A FormatError reports that the input is not a valid woz file. -type FormatError string - -func (e FormatError) Error() string { return "woz: invalid format: " + string(e) } - -type CRCError struct { - Declared uint32 - Computed uint32 -} - -func (e CRCError) Error() string { - return fmt.Sprintf("woz: failed checksum: declared=%d; computed=%d", e.Declared, e.Computed) -} - -func (d *decoder) info(format string, args ...interface{}) { - if !strings.HasSuffix(format, "\n") { - format += "\n" - } - fmt.Printf("INFO: "+format, args...) -} - -func (d *decoder) warn(format string, args ...interface{}) { - if !strings.HasSuffix(format, "\n") { - format += "\n" - } - fmt.Printf("WARN: "+format, args...) -} - -func (d *decoder) checkHeader() error { - _, err := io.ReadFull(d.r, d.tmp[:len(wozHeader)]) - if err != nil { - return err - } - if string(d.tmp[:len(wozHeader)]) != wozHeader { - return FormatError("not a woz file") - } - return binary.Read(d.r, binary.LittleEndian, &d.crcVal) -} - -func (d *decoder) parseChunk() (done bool, err error) { - // Read the chunk type and length - n, err := io.ReadFull(d.r, d.tmp[:8]) - if err != nil { - if n == 0 && err == io.EOF { - return true, nil - } - return false, err - } - length := binary.LittleEndian.Uint32(d.tmp[4:8]) - d.crc.Write(d.tmp[:8]) - switch string(d.tmp[:4]) { - case "INFO": - return false, d.parseINFO(length) - case "TMAP": - return false, d.parseTMAP(length) - case "TRKS": - return false, d.parseTRKS(length) - case "META": - return false, d.parseMETA(length) - default: - return false, d.parseUnknown(string(d.tmp[:4]), length) - } -} - -func (d *decoder) parseINFO(length uint32) error { - d.info("INFO chunk!\n") - if length != 60 { - d.warn("expected INFO chunk length of 60; got %d", length) - } - if _, err := io.ReadFull(d.r, d.tmp[:length]); err != nil { - return err - } - d.crc.Write(d.tmp[:length]) - - d.woz.Info.Version = d.tmp[0] - d.woz.Info.DiskType = DiskType(d.tmp[1]) - d.woz.Info.WriteProtected = d.tmp[2] == 1 - d.woz.Info.Synchronized = d.tmp[3] == 1 - d.woz.Info.Cleaned = d.tmp[4] == 1 - d.woz.Info.Creator = strings.TrimRight(string(d.tmp[5:37]), " ") - - return nil -} - -func (d *decoder) parseTMAP(length uint32) error { - d.info("TMAP chunk!\n") - if length != 160 { - d.warn("expected TMAP chunk length of 160; got %d", length) - } - if _, err := io.ReadFull(d.r, d.woz.TMap[:]); err != nil { - return err - } - d.crc.Write(d.woz.TMap[:]) - return nil -} - -func (d *decoder) parseTRKS(length uint32) error { - d.info("TRKS chunk!\n") - if length%TrackLength != 0 { - return FormatError(fmt.Sprintf("expected TRKS chunk length to be a multiple of %d; got %d", TrackLength, length)) - } - buf := make([]byte, length) - if _, err := io.ReadFull(d.r, buf); err != nil { - return err - } - d.crc.Write(buf) - - for offset := 0; offset < int(length); offset += TrackLength { - b := buf[offset : offset+TrackLength] - t := TRK{ - BytesUsed: binary.LittleEndian.Uint16(b[6646:6648]), - BitCount: binary.LittleEndian.Uint16(b[6648:6650]), - SplicePoint: binary.LittleEndian.Uint16(b[6650:6652]), - SpliceNibble: b[6652], - SpliceBitCount: b[6653], - Reserved: binary.LittleEndian.Uint16(b[6654:6656]), - } - copy(t.BitStream[:], b) - d.woz.TRKS = append(d.woz.TRKS, t) - } - - // type TRK struct { - // Bitstream [6646]uint8 - // BytesUsed uint16 - // BitCount uint16 - // SplicePoint uint16 - // SpliceNibble uint8 - // SpliceBitCount uint8 - // Reserved uint16 - // } - - return nil -} - -func (d *decoder) parseMETA(length uint32) error { - d.info("META chunk!\n") - buf := make([]byte, length) - if _, err := io.ReadFull(d.r, buf); err != nil { - return err - } - d.crc.Write(buf) - rows := strings.Split(string(buf), "\n") - m := &d.woz.Metadata - m.RawValues = make(map[string]string, len(rows)) - for _, row := range rows { - parts := strings.SplitN(row, "\t", 2) - if len(parts) == 0 { - return FormatError("empty metadata line") - } - if len(parts) == 1 { - return FormatError("strange metadata line with no tab: " + parts[0]) - } - m.Keys = append(m.Keys, parts[0]) - m.RawValues[parts[0]] = parts[1] - } - - return nil -} - -func (d *decoder) parseUnknown(id string, length uint32) error { - d.info("unknown chunk type (%s): ignoring\n", id) - buf := make([]byte, length) - if _, err := io.ReadFull(d.r, buf); err != nil { - return err - } - d.crc.Write(buf) - d.woz.Unknowns = append(d.woz.Unknowns, UnknownChunk{ID: id, Data: buf}) - return nil -} - -// Decode reads a woz disk image from r and returns it as a *Woz. -func Decode(r io.Reader) (*Woz, error) { - d := &decoder{ - r: r, - crc: crc32.NewIEEE(), - woz: &Woz{}, - } - if err := d.checkHeader(); err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return nil, err - } - - // Read all chunks. - for { - done, err := d.parseChunk() - if err != nil { - return nil, err - } - if done { - break - } - } - - // Check CRC. - if d.crcVal != d.crc.Sum32() { - return d.woz, CRCError{Declared: d.crcVal, Computed: d.crc.Sum32()} - } - - return d.woz, nil -} diff --git a/woz/woz_test.go b/woz/woz_test.go deleted file mode 100644 index 1a4544e..0000000 --- a/woz/woz_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package woz_test - -import ( - "bytes" - "testing" - - "github.com/zellyn/diskii/data" - "github.com/zellyn/diskii/woz" -) - -func TestBasicLoad(t *testing.T) { - wz, err := woz.Decode(bytes.NewReader(data.DOS33masterWOZ)) - if err != nil { - t.Fatal(err) - } - if len(wz.Unknowns) > 0 { - t.Fatalf("want 0 unknowns; got %d", len(wz.Unknowns)) - } - // fmt.Printf("%#v\n", wz) - // t.Fatal("NOTHING") -}