Skip to content

Commit

Permalink
working on disk formats
Browse files Browse the repository at this point in the history
  • Loading branch information
zellyn committed Jun 7, 2018
1 parent 510a7d2 commit 6d57f2d
Show file tree
Hide file tree
Showing 18 changed files with 336 additions and 30 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ will be likely to get priority.
- http://retrocomputingaustralia.com/rca-downloads/ Michael Mulhern's MacOS package of CiderPress
- http://applecommander.sourceforge.net/ - the commandline, cross-platform alternative to CiderPress
- http://brutaldeluxe.fr/products/crossdevtools/cadius/index.html - Brutal Deluxe's commandline tools
- https://github.com/paleotronic/dskalyzer - cross-platform disk analysis tool (also written in Go!) from the folks who brought you [Ocalyzer](http://octalyzer.com/).
- https://github.com/cybernesto/dsktool.rb
- https://github.com/cmosher01/Apple-II-Disk-Tools
- https://github.com/madsen/perl-libA2
Expand All @@ -105,3 +106,4 @@ will be likely to get priority.
- https://github.com/dmolony/DiskBrowser - graphical (Java) disk browser that knows how to interpret and display many file formats
- https://github.com/slotek/apple2-disk-util - ruby
- https://github.com/slotek/dsk2nib - C
- https://github.com/robmcmullen/atrcopy - dos3.3, python
5 changes: 5 additions & 0 deletions cmd/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
)

var shortnames bool // flag for whether to print short filenames
var debug bool

// catalogCmd represents the cat command, used to catalog a disk or
// directory.
Expand All @@ -30,6 +31,7 @@ var catalogCmd = &cobra.Command{
func init() {
RootCmd.AddCommand(catalogCmd)
catalogCmd.Flags().BoolVarP(&shortnames, "shortnames", "s", false, "whether to print short filenames (only makes a difference on Super-Mon disks)")
catalogCmd.Flags().BoolVarP(&debug, "debug", "d", false, "pring debug information")
}

// runCat performs the actual catalog logic.
Expand All @@ -41,6 +43,9 @@ func runCat(args []string) error {
if err != nil {
return err
}
if debug {
fmt.Printf("Got disk of type %q with underlying sector/block order %q.\n", op.Name(), op.Order())
}
subdir := ""
if len(args) == 2 {
if !op.HasSubdirs() {
Expand Down
85 changes: 55 additions & 30 deletions data/data.go

Large diffs are not rendered by default.

Binary file added data/disks/dos33master.woz
Binary file not shown.
Binary file added data/disks/lode-runner-disk-1.dsk
Binary file not shown.
Binary file added data/disks/lode-runner-disk-1.po
Binary file not shown.
Binary file added data/disks/sam-hardware-ver.po
Binary file not shown.
5 changes: 5 additions & 0 deletions lib/disk/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ 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)
Expand Down
14 changes: 14 additions & 0 deletions lib/disk/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type SectorDisk interface {
Tracks() byte
// Write writes the disk contents to the given file.
Write(io.Writer) (int, error)
// Order returns the sector order.
Order() string
}

// LogicalSectorDisk is the interface used to read and write a disk by
Expand All @@ -86,6 +88,8 @@ type LogicalSectorDisk interface {
Tracks() byte
// Write writes the disk contents to the given file.
Write(io.Writer) (int, error)
// Order returns the underlying sector ordering.
Order() string
}

// MappedDisk wraps a SectorDisk as a LogicalSectorDisk, handling the
Expand Down Expand Up @@ -156,6 +160,11 @@ func (md MappedDisk) Write(w io.Writer) (n int, err error) {
return md.sectorDisk.Write(w)
}

// Order returns the sector order of the underlying sector disk.
func (md MappedDisk) Order() string {
return md.sectorDisk.Order()
}

// OpenDisk opens a disk image by filename.
func OpenDisk(filename string) (SectorDisk, error) {
ext := strings.ToLower(path.Ext(filename))
Expand Down Expand Up @@ -284,6 +293,11 @@ func (dbv DiskBlockDevice) Blocks() uint16 {
return dbv.blocks
}

// Order returns the underlying sector or block order of the storage.
func (dbv DiskBlockDevice) Order() string {
return dbv.lsd.Order()
}

// Write writes the device contents to the given Writer.
func (dbv DiskBlockDevice) Write(w io.Writer) (int, error) {
return dbv.lsd.Write(w)
Expand Down
8 changes: 8 additions & 0 deletions lib/disk/dsk.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type DSK struct {
physicalToStored []byte // Map of physical on-disk sector numbers to sectors in the disk image
bytesPerTrack int // Number of bytes per track
tracks byte // Number of tracks
order string // Underlying sector order.
}

var _ SectorDisk = (*DSK)(nil)
Expand All @@ -37,6 +38,7 @@ func LoadDSK(filename string) (DSK, error) {
physicalToStored: Dos33PhysicalToLogicalSectorMap,
bytesPerTrack: 16 * 256,
tracks: DOS33Tracks,
order: "dos33",
}, nil
}

Expand All @@ -48,6 +50,7 @@ func Empty() DSK {
physicalToStored: Dos33PhysicalToLogicalSectorMap,
bytesPerTrack: 16 * 256,
tracks: DOS33Tracks,
order: "dos33",
}
}

Expand Down Expand Up @@ -97,6 +100,11 @@ func (d DSK) Tracks() byte {
return d.tracks
}

// Order returns the sector order name.
func (d DSK) Order() string {
return d.order
}

// Write writes the disk contents to the given file.
func (d DSK) Write(w io.Writer) (n int, err error) {
return w.Write(d.data)
Expand Down
2 changes: 2 additions & 0 deletions lib/disk/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type BlockDevice interface {
Blocks() uint16
// Write writes the device contents to the given Writer.
Write(io.Writer) (int, error)
// Order returns the sector or block order of the underlying device.
Order() string
}

// SectorSource is the interface for types that can marshal to sectors.
Expand Down
2 changes: 2 additions & 0 deletions lib/disk/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type Descriptor struct {
type Operator interface {
// Name returns the name of the operator.
Name() string
// Order returns the sector or block order name.
Order() string
// HasSubdirs returns true if the underlying operating system on the
// disk allows subdirectories.
HasSubdirs() bool
Expand Down
5 changes: 5 additions & 0 deletions lib/dos3/dos3.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,11 @@ func (o operator) Name() string {
return operatorName
}

// Order returns the sector or block order of the underlying storage.
func (o operator) Order() string {
return o.lsd.Order()
}

// HasSubdirs returns true if the underlying operating system on the
// disk allows subdirectories.
func (o operator) HasSubdirs() bool {
Expand Down
5 changes: 5 additions & 0 deletions lib/prodos/prodos.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,11 @@ func (o operator) Name() string {
return operatorName
}

// Order returns the sector or block order of the underlying storage.
func (o operator) Order() string {
return o.dev.Order()
}

// HasSubdirs returns true if the underlying operating system on the
// disk allows subdirectories.
func (o operator) HasSubdirs() bool {
Expand Down
5 changes: 5 additions & 0 deletions lib/supermon/supermon.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,11 @@ func (o Operator) Name() string {
return operatorName
}

// Order returns the sector or block order of the Operator.
func (o Operator) Order() string {
return o.SD.Order()
}

// HasSubdirs returns true if the underlying operating system on the
// disk allows subdirectories.
func (o Operator) HasSubdirs() bool {
Expand Down
201 changes: 201 additions & 0 deletions lib/woz/woz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package woz

import (
"encoding/binary"
"fmt"
"hash"
"hash/crc32"
"io"
"strings"
)

const wozHeader = "WOZ1\xFF\n\r\n"

type Woz struct {
Info Info
Unknowns []UnknownChunk
}

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 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 = format + "\n"
}
fmt.Printf("INFO: "+format, args...)
}

func (d *decoder) warn(format string, args ...interface{}) {
if !strings.HasSuffix(format, "\n") {
format = 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")
}
if err := binary.Read(d.r, binary.LittleEndian, &d.crcVal); err != nil {
return err
}
return nil
}

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)
}

return false, nil
}

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])
return nil
}

func (d *decoder) parseTMAP(length uint32) error {
d.info("TMAP chunk!\n")
buf := make([]byte, length)
if _, err := io.ReadFull(d.r, buf); err != nil {
return err
}
d.crc.Write(buf)
return nil
}

func (d *decoder) parseTRKS(length uint32) error {
d.info("TRKS chunk!\n")
buf := make([]byte, length)
if _, err := io.ReadFull(d.r, buf); err != nil {
return err
}
d.crc.Write(buf)
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)
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
}
Loading

0 comments on commit 6d57f2d

Please sign in to comment.