working on disk formats

This commit is contained in:
Zellyn Hunter 2018-06-06 22:27:15 -04:00
parent 510a7d2ac8
commit 6d57f2de51
18 changed files with 336 additions and 30 deletions

View File

@ -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
@ -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

View File

@ -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.
@ -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.
@ -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() {

File diff suppressed because one or more lines are too long

BIN
data/disks/dos33master.woz Normal file

Binary file not shown.

BIN
data/disks/lode-runner-disk-1.dsk Executable file

Binary file not shown.

BIN
data/disks/lode-runner-disk-1.po Executable file

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

@ -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)
@ -37,6 +38,7 @@ func LoadDSK(filename string) (DSK, error) {
physicalToStored: Dos33PhysicalToLogicalSectorMap,
bytesPerTrack: 16 * 256,
tracks: DOS33Tracks,
order: "dos33",
}, nil
}
@ -48,6 +50,7 @@ func Empty() DSK {
physicalToStored: Dos33PhysicalToLogicalSectorMap,
bytesPerTrack: 16 * 256,
tracks: DOS33Tracks,
order: "dos33",
}
}
@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

201
lib/woz/woz.go Normal file
View File

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

21
lib/woz/woz_test.go Normal file
View File

@ -0,0 +1,21 @@
package woz_test
import (
"bytes"
"testing"
"github.com/zellyn/diskii/data"
"github.com/zellyn/diskii/lib/woz"
)
func TestBasicLoad(t *testing.T) {
bb := data.MustAsset("data/disks/dos33master.woz")
wz, err := woz.Decode(bytes.NewReader(bb))
if err != nil {
t.Fatal(err)
}
if len(wz.Unknowns) > 0 {
t.Fatalf("want 0 unknowns; got %d", len(wz.Unknowns))
}
t.Fatal("NOTHING")
}

6
make-data.sh Executable file
View File

@ -0,0 +1,6 @@
go-bindata -pkg data -o data/data.go \
data/disks/ProDOS_2_4_1.dsk \
data/disks/dos33master.woz \
data/boot/prodos-new-boot0.bin \
data/boot/prodos-old-boot0.bin
goimports -w data/data.go