mirror of
https://github.com/zellyn/diskii.git
synced 2025-01-29 14:29:55 +00:00
working on disk formats
This commit is contained in:
parent
510a7d2ac8
commit
6d57f2de51
@ -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
|
||||
|
@ -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() {
|
||||
|
85
data/data.go
85
data/data.go
File diff suppressed because one or more lines are too long
BIN
data/disks/dos33master.woz
Normal file
BIN
data/disks/dos33master.woz
Normal file
Binary file not shown.
BIN
data/disks/lode-runner-disk-1.dsk
Executable file
BIN
data/disks/lode-runner-disk-1.dsk
Executable file
Binary file not shown.
BIN
data/disks/lode-runner-disk-1.po
Executable file
BIN
data/disks/lode-runner-disk-1.po
Executable file
Binary file not shown.
BIN
data/disks/sam-hardware-ver.po
Normal file
BIN
data/disks/sam-hardware-ver.po
Normal file
Binary file not shown.
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
201
lib/woz/woz.go
Normal 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
21
lib/woz/woz_test.go
Normal 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
6
make-data.sh
Executable 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
|
Loading…
x
Reference in New Issue
Block a user