mirror of
https://github.com/zellyn/diskii.git
synced 2025-01-13 03:29:49 +00:00
more lints, removed woz
This commit is contained in:
parent
23c9b1edcf
commit
c2dd6362de
@ -1,3 +1,5 @@
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
linters:
|
||||
enable:
|
||||
- gocritic
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
2
cmd/doc.go
Normal file
2
cmd/doc.go
Normal file
@ -0,0 +1,2 @@
|
||||
// Package cmd contains the actual command implementations.
|
||||
package cmd
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 == "-" {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Package data is a bunch of go:embed embedded files.
|
||||
package data
|
||||
|
||||
import _ "embed"
|
||||
|
73
disk/dev.go
73
disk/dev.go
@ -1,73 +0,0 @@
|
||||
// Copyright © 2017 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// 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)
|
||||
}
|
@ -65,3 +65,6 @@ type TrackSector struct {
|
||||
Track byte
|
||||
Sector byte
|
||||
}
|
||||
|
||||
// Block is a ProDOS block: 512 bytes.
|
||||
type Block [512]byte
|
||||
|
18
disk/open.go
18
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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
14
types/ops.go
14
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("")
|
||||
)
|
||||
|
||||
|
@ -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.
|
||||
|
268
woz/woz.go
268
woz/woz.go
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user