catalog working for all types, reorder added

This commit is contained in:
Zellyn Hunter 2021-07-12 16:27:13 -04:00
parent 9c66e2c5e6
commit ef9115dcaf
21 changed files with 386 additions and 185 deletions

View File

@ -47,8 +47,8 @@ Current disk operations supported:
| ---------------- | -------- | ------ | ------------------ |
| basic structures | ✓ | ✓ | ✓ |
| ls | ✓ | ✓ | ✓ |
| dump | ✓ | ✗ | ✓ |
| put | ✗ | ✗ | |
| dump | ✗ | ✗ | ✗ |
| put | ✗ | ✗ | |
| dumptext | ✗ | ✗ | ✗ |
| delete | ✗ | ✗ | ✗ |
| rename | ✗ | ✗ | ✗ |
@ -78,12 +78,9 @@ will be likely to get priority.
- [x] Implement `GetFile` for DOS 3.3
- [ ] Add and implement the `-l` flag for `ls`
- [x] Add `Delete` to the `disk.Operator` interface
- [x] Implement it for Super-Mon
- [ ] Implement it for Super-Mon
- [ ] Implement it for DOS 3.3
- [ ] Make 13-sector DOS disks work
- [ ] Read/write nybble formats
- [ ] Read/write gzipped files
- [ ] Add basic ProDOS structures
- [x] Add basic ProDOS structures
- [ ] Add ProDOS support
# Related tools
@ -114,38 +111,34 @@ will be likely to get priority.
- `.do`
- `.po`
- `.dsk` - could be DO or PO.
- `.dsk` - could be DO or PO. When in doubt, assume DO.
DOS 3.2.1: the 13 sectors are physically skewed on disk.
DOS 3.3+: the 16 physical sectors are stored in ascending order on disk, not physically skewed at all. The
| Logical Sector | DOS 3.3 Physical Sector | ProDOS Physical Sector |
| --------------- | -------------- | ------------- |
| 0 | 0 | x |
| 1 | D | x |
| 2 | B | x |
| 3 | 9 | x |
| 4 | 7 | x |
| 5 | 5 | x |
| 6 | 3 | x |
| 7 | 1 | x |
| 8 | E | x |
| 9 | C | x |
| A | A | x |
| B | 8 | x |
| C | 6 | x |
| D | 4 | x |
| E | 2 | x |
| F | F | x |
| Physical Sectors | DOS 3.2 Logical | DOS 3.3 Logical | ProDOS/Pascal Logical | CP/M Logical |
|------------------|-----------------|-----------------|-----------------------|------------- |
| 0 | 0 | 0 | 0.0 | 0.0 |
| 1 | 1 | 7 | 4.0 | 2.3 |
| 2 | 2 | E | 0.1 | 1.2 |
| 3 | 3 | 6 | 4.1 | 0.1 |
| 4 | 4 | D | 1.0 | 3.0 |
| 5 | 5 | 5 | 5.0 | 1.3 |
| 6 | 6 | C | 1.1 | 0.2 |
| 7 | 7 | 4 | 5.1 | 3.1 |
| 8 | 8 | B | 2.0 | 2.0 |
| 9 | 9 | 3 | 6.0 | 0.3 |
| A | A | A | 2.1 | 3.2 |
| B | B | 2 | 6.1 | 2.1 |
| C | C | 9 | 3.0 | 1.0 |
| D | | 1 | 7.0 | 3.3 |
| E | | 8 | 3.1 | 2.2 |
| F | | F | 7.1 | 1.1 |
_Note: DOS 3.2 rearranged the physical sectors on disk to achieve interleaving._
### RWTS - DOS
Sector mapping:
http://www.textfiles.com/apple/ANATOMY/rwts.s.txt and search for INTRLEAV
Mapping from specified sector to physical sector (the reverse of what the comment says):
Mapping from specified sector to physical sector:
`00 0D 0B 09 07 05 03 01 0E 0C 0A 08 06 04 02 0F`

View File

@ -10,19 +10,19 @@ import (
"github.com/zellyn/diskii/types"
)
var shortnames bool // flag for whether to print short filenames
var debug bool
type LsCmd struct {
Order string `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.'"`
ShortNames bool `kong:"short='s',help='Whether to print short filenames (only makes a difference on Super-Mon disks).'"`
Image *os.File `kong:"arg,required,help='Disk/device image to read.'"`
Directory string `kong:"arg,optional,help='Directory to list (ProDOS only).'"`
}
func (l *LsCmd) Run(globals *types.Globals) error {
op, order, err := disk.OpenImage(l.Image, globals)
op, order, err := disk.OpenImage(l.Image, l.Order, l.System, globals)
if err != nil {
return err
return fmt.Errorf("%w: %s", err, l.Image.Name())
}
if globals.Debug {
fmt.Fprintf(os.Stderr, "Opened disk with order %q, system %q\n", order, op.Name())
@ -38,7 +38,7 @@ func (l *LsCmd) Run(globals *types.Globals) error {
return err
}
for _, fd := range fds {
if !shortnames && fd.Fullname != "" {
if !l.ShortNames && fd.Fullname != "" {
fmt.Println(fd.Fullname)
} else {
fmt.Println(fd.Name)

101
cmd/reorder.go Normal file
View File

@ -0,0 +1,101 @@
package cmd
import (
"fmt"
"path"
"strings"
"github.com/zellyn/diskii/disk"
"github.com/zellyn/diskii/helpers"
"github.com/zellyn/diskii/types"
)
type ReorderCmd struct {
Order string `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
NewOrder string `kong:"default='auto',enum='auto,do,po',help='New Logical-to-physical sector order.'"`
Force bool `kong:"short='s',help='Overwrite existing file?'"`
DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to read.'"`
NewDiskImage string `kong:"arg,optional,type='path',help='Disk image to write, if different.'"`
}
func (r *ReorderCmd) Run(globals *types.Globals) error {
fromOrderName, toOrderName, err := getOrders(r.DiskImage, r.Order, r.NewDiskImage, r.NewOrder)
if err != nil {
return err
}
frombytes, err := helpers.FileContentsOrStdIn(r.DiskImage)
if err != nil {
return err
}
fromOrder, ok := disk.LogicalToPhysicalByName[fromOrderName]
if !ok {
return fmt.Errorf("internal error: disk order '%s' not found", fromOrderName)
}
toOrder, ok := disk.PhysicalToLogicalByName[toOrderName]
if !ok {
return fmt.Errorf("internal error: disk order '%s' not found", toOrderName)
}
rawbytes, err := disk.Swizzle(frombytes, fromOrder)
if err != nil {
return err
}
tobytes, err := disk.Swizzle(rawbytes, toOrder)
if err != nil {
return err
}
return helpers.WriteOutput(r.NewDiskImage, tobytes, r.DiskImage, r.Force)
}
// getOrders returns the input order, and the output order.
func getOrders(inFilename string, inOrder string, outFilename string, outOrder string) (string, string, error) {
if inOrder == "auto" && outOrder != "auto" {
return oppositeOrder(outOrder), outOrder, nil
}
if outOrder == "auto" && inOrder != "auto" {
return inOrder, oppositeOrder(inOrder), nil
}
if inOrder != outOrder {
return inOrder, outOrder, nil
}
if inOrder != "auto" {
return "", "", fmt.Errorf("identical order and new-order")
}
inGuess, outGuess := orderFromFilename(inFilename), orderFromFilename(outFilename)
if inGuess == outGuess {
if inGuess == "" {
return "", "", fmt.Errorf("cannot determine input or output order from file extensions")
}
return "", "", fmt.Errorf("guessed order (%s) from file %q is the same as guessed order (%s) from file %q", inGuess, inFilename, outGuess, outFilename)
}
if inGuess == "" {
return oppositeOrder(outGuess), outGuess, nil
}
if outGuess == "" {
return inGuess, oppositeOrder(inGuess), nil
}
return inGuess, outGuess, nil
}
// oppositeOrder returns the opposite order from the input.
func oppositeOrder(order string) string {
if order == "do" {
return "po"
}
return "do"
}
// orderFromFilename tries to guess the disk order from the filename, using the extension.
func orderFromFilename(filename string) string {
ext := strings.ToLower(path.Ext(filename))
switch ext {
case ".dsk", ".do":
return "do"
case ".po":
return "po"
default:
return ""
}
}

View File

@ -3,16 +3,20 @@ package data
import _ "embed"
// DOS 3.3 Master Disk.
//go:embed disks/dos33mst.dsk
//go:embed disks/dos33master.dsk
var DOS33master_dsk []byte
// DOS 3.3 Master Disk, as a .woz file.
//go:embed disks/dos33master.woz
var DOS33master_woz []byte
// John Brooks' update to ProDOS.
// Website: https://prodos8.com
// Announcements: https://www.callapple.org/author/jbrooks/
//go:embed disks/ProDOS_2_4_2.dsk
var ProDOS242_dsk []byte
//go:embed disks/ProDOS_2_4_2.po
var ProDOS242_po []byte
// The new ProDOS sector 0, used on and after the IIGS System 4.0.
// The new ProDOS sector 0, used on and after the IIGS System 4.0. Understands sparse PRODOS.SYSTEM files.
//go:embed boot/prodos-new-boot0.bin
var ProDOSNewBootSector0 []byte

Binary file not shown.

Binary file not shown.

BIN
data/disks/ProDOS_2_4_2.po Normal file

Binary file not shown.

View File

@ -11,7 +11,7 @@ import (
)
// A ProDOS block.
type Block [256]byte
type Block [512]byte
// Dev represents a .po disk image.
type Dev struct {

View File

@ -41,6 +41,20 @@ var ProDosPhysicalToLogicalSectorMap = []int{
0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F,
}
// LogicalToPhysicalByName maps from "do" and "po" to the corresponding
// logical-to-physical ordering.
var LogicalToPhysicalByName map[string][]int = map[string][]int{
"do": Dos33LogicalToPhysicalSectorMap,
"po": ProDOSLogicalToPhysicalSectorMap,
}
// PhysicalToLogicalByName maps from "do" and "po" to the corresponding
// physical-to-logical ordering.
var PhysicalToLogicalByName map[string][]int = map[string][]int{
"do": Dos33PhysicalToLogicalSectorMap,
"po": ProDosPhysicalToLogicalSectorMap,
}
// TrackSector is a pair of track/sector bytes.
type TrackSector struct {
Track byte

View File

@ -10,36 +10,30 @@ import (
"github.com/zellyn/diskii/types"
)
var diskOrdersByName map[string][]int = map[string][]int{
"do": Dos33LogicalToPhysicalSectorMap,
"po": ProDOSLogicalToPhysicalSectorMap,
"raw": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF},
}
// OpenImage attempts to open an image on disk, using the provided ordering and system type.
func OpenImage(file *os.File, globals *types.Globals) (types.Operator, string, error) {
func OpenImage(file *os.File, order string, system string, globals *types.Globals) (types.Operator, string, error) {
bb, err := io.ReadAll(file)
if err != nil {
return nil, "", err
}
if len(bb) == FloppyDiskBytes {
return openDoOrPo(bb, globals, strings.ToLower(path.Ext(file.Name())))
return openDoOrPo(bb, order, system, globals, strings.ToLower(path.Ext(file.Name())))
}
return nil, "", fmt.Errorf("OpenImage not implemented yet for non-disk-sized images")
}
func openDoOrPo(diskbytes []byte, globals *types.Globals, ext string) (types.Operator, string, error) {
func openDoOrPo(diskbytes []byte, order string, system string, globals *types.Globals, ext string) (types.Operator, string, error) {
var factories []types.OperatorFactory
for _, factory := range globals.DiskOperatorFactories {
if globals.System == "auto" || globals.System == factory.Name() {
if system == "auto" || system == factory.Name() {
factories = append(factories, factory)
}
}
if len(factories) == 0 {
return nil, "", fmt.Errorf("cannot find disk system with name %q", globals.System)
return nil, "", fmt.Errorf("cannot find disk system with name %q", system)
}
orders := []string{globals.Order}
switch globals.Order {
orders := []string{order}
switch order {
case "do", "po":
// nothing more
case "auto":
@ -54,16 +48,16 @@ func openDoOrPo(diskbytes []byte, globals *types.Globals, ext string) (types.Ope
return nil, "", fmt.Errorf("unknown disk image extension: %q", ext)
}
default:
return nil, "", fmt.Errorf("disk order %q invalid for %d-byte disk images", globals.Order, FloppyDiskBytes)
return nil, "", fmt.Errorf("disk order %q invalid for %d-byte disk images", order, FloppyDiskBytes)
}
for _, order := range orders {
swizzled, err := Swizzle(diskbytes, diskOrdersByName[order])
swizzled, err := Swizzle(diskbytes, LogicalToPhysicalByName[order])
if err != nil {
return nil, "", err
}
for _, factory := range factories {
if len(orders) == 1 && globals.System != "auto" {
if len(orders) == 1 && system != "auto" {
if globals.Debug {
fmt.Fprintf(os.Stderr, "Attempting to open with order=%s, system=%s.\n", order, factory.Name())
}
@ -88,14 +82,14 @@ func openDoOrPo(diskbytes []byte, globals *types.Globals, ext string) (types.Ope
}
}
}
return nil, "", fmt.Errorf("openDoOrPo not implemented yet")
return nil, "", fmt.Errorf("unabled to open disk image")
}
// Swizzle changes the sector ordering according to the order parameter. If
// order is nil, it leaves the order unchanged.
func Swizzle(diskimage []byte, order []int) ([]byte, error) {
if len(diskimage) != FloppyDiskBytes {
return nil, fmt.Errorf("swizzling only works on disk images of %d bytes; got %d", FloppyDiskBytes, len(diskimage))
return nil, fmt.Errorf("reordering only works on disk images of %d bytes; got %d", FloppyDiskBytes, len(diskimage))
}
if err := validateOrder(order); err != nil {
return nil, fmt.Errorf("called Swizzle with weird order: %w", err)

View File

@ -7,7 +7,6 @@ package dos3
import (
"encoding/binary"
"fmt"
"os"
"strings"
"github.com/zellyn/diskii/disk"
@ -500,9 +499,6 @@ func readCatalogSectors(diskbytes []byte, debug bool) ([]CatalogSector, error) {
if err := v.Validate(); err != nil {
return nil, fmt.Errorf("Invalid VTOC sector: %v", err)
}
if debug {
fmt.Fprintf(os.Stderr, "Read VTOC sector: %#v\n", v)
}
nextTrack := v.CatalogTrack
nextSector := v.CatalogSector
@ -686,9 +682,13 @@ func (of OperatorFactory) Name() string {
// SeemsToMatch returns true if the []byte disk image seems to match the
// system of this operator.
func (of OperatorFactory) SeemsToMatch(diskbytes []byte, debug bool) bool {
func (of OperatorFactory) SeemsToMatch(rawbytes []byte, debug bool) bool {
// For now, just return true if we can run Catalog successfully.
_, _, err := ReadCatalog(diskbytes, debug)
swizzled, err := of.swizzle(rawbytes)
if err != nil {
return false
}
_, _, err = ReadCatalog(swizzled, debug)
if err != nil {
return false
}
@ -696,6 +696,14 @@ func (of OperatorFactory) SeemsToMatch(diskbytes []byte, debug bool) bool {
}
// Operator returns an Operator for the []byte disk image.
func (of OperatorFactory) Operator(diskbytes []byte, debug bool) (types.Operator, error) {
return operator{data: diskbytes, debug: debug}, nil
func (of OperatorFactory) Operator(rawbytes []byte, debug bool) (types.Operator, error) {
swizzled, err := of.swizzle(rawbytes)
if err != nil {
return nil, err
}
return operator{data: swizzled, debug: debug}, nil
}
func (of OperatorFactory) swizzle(rawbytes []byte) ([]byte, error) {
return disk.Swizzle(rawbytes, disk.Dos33PhysicalToLogicalSectorMap)
}

View File

@ -5,7 +5,10 @@
package helpers
import (
"io/ioutil"
"errors"
"fmt"
"io"
"io/fs"
"os"
)
@ -13,7 +16,23 @@ import (
// is "-", in which case it reads from stdin.
func FileContentsOrStdIn(s string) ([]byte, error) {
if s == "-" {
return ioutil.ReadAll(os.Stdin)
return io.ReadAll(os.Stdin)
}
return ioutil.ReadFile(s)
return os.ReadFile(s)
}
func WriteOutput(outfilename string, contents []byte, infilename string, force bool) error {
if outfilename == "" {
outfilename = infilename
}
if outfilename == "-" {
_, err := os.Stdout.Write(contents)
return err
}
if !force {
if _, err := os.Stat(outfilename); !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("cannot overwrite file %q without --force (-f)", outfilename)
}
}
return os.WriteFile(outfilename, contents, 0666)
}

17
main.go
View File

@ -5,6 +5,8 @@ package main
import (
"github.com/zellyn/diskii/cmd"
"github.com/zellyn/diskii/dos3"
"github.com/zellyn/diskii/prodos"
"github.com/zellyn/diskii/supermon"
"github.com/zellyn/diskii/types"
"fmt"
@ -14,11 +16,10 @@ import (
)
var cli struct {
Debug bool `kong:"short='v',help='Enable debug mode.'"`
Order string `kong:"default='auto',enum='auto,raw,do,po',help='Logical-to-physical sector order.'"`
System string `kong:"default='auto',enum='auto,dos3',help='DOS system used for image.'"`
Debug bool `kong:"short='v',help='Enable debug mode.'"`
Ls cmd.LsCmd `cmd:"" aliases:"cat,catalog" help:"List paths."`
Ls cmd.LsCmd `cmd:"" aliases:"cat,catalog" help:"List paths."`
Reorder cmd.ReorderCmd `cmd:"" help:"Reorder disk images."`
}
func run() error {
@ -34,13 +35,11 @@ func run() error {
)
globals := &types.Globals{
Debug: cli.Debug,
Order: cli.Order,
System: cli.System,
Debug: cli.Debug,
DiskOperatorFactories: []types.OperatorFactory{
dos3.OperatorFactory{},
// supermon.OperatorFactory,
// prodos.DiskOperatorFactory,
supermon.OperatorFactory{},
prodos.OperatorFactory{},
},
}
// Call the Run() method of the selected parsed command.

View File

@ -85,3 +85,8 @@ F8 CLD
echo -n -e '\x20\x40\x03\x6D\x01\xDC\x2C\x02\xDF\x2C\x00\x60\xF8\x4C\x00\x60' | diskii put -f ./lib/supermon/testdata/chacha20.dsk DF01:FHELLO -
echo -n -e '\x20\x58\xFC\xA2\x00\xBD\x13\x60\xF0\x06\x20\xED\xFD\xE8\xD0\xF5\x4C\x10\x60\xC8\xC5\xCC\xCC\xCF\xAC\xA0\xD7\xCF\xD2\xCC\xC4\x00' | diskii put -f ./lib/supermon/testdata/chacha20.dsk DF02:FWORLD -
* Sources
** ProDOS
[[https://www.apple.asimov.net/documentation/source_code/Apple%20ProDOS%20Boot%20Source.pdf][ProDOS boot source]]

View File

@ -7,9 +7,9 @@ package prodos
import (
"encoding/binary"
"fmt"
"io"
"github.com/zellyn/diskii/disk"
"github.com/zellyn/diskii/types"
)
// Storage types.
@ -104,11 +104,11 @@ func (vbm VolumeBitMap) IsFree(block uint16) bool {
// readVolumeBitMap reads the entire volume bitmap from a block
// device.
func readVolumeBitMap(bd disk.BlockDevice, startBlock uint16) (VolumeBitMap, error) {
blocks := bd.Blocks() / 4096
func readVolumeBitMap(devicebytes []byte, startBlock uint16) (VolumeBitMap, error) {
blocks := uint16(len(devicebytes) / 512 / 4096)
vbm := NewVolumeBitMap(startBlock, blocks)
for i := 0; i < len(vbm); i++ {
if err := disk.UnmarshalBlock(bd, &vbm[i], vbm[i].GetBlock()); err != nil {
if err := disk.UnmarshalBlock(devicebytes, &vbm[i], vbm[i].GetBlock()); err != nil {
return nil, fmt.Errorf("cannot read block %d (device block %d) of Volume Bit Map: %v", i, vbm[i].GetBlock(), err)
}
}
@ -116,9 +116,9 @@ func readVolumeBitMap(bd disk.BlockDevice, startBlock uint16) (VolumeBitMap, err
}
// Write writes the Volume Bit Map to a block device.
func (vbm VolumeBitMap) Write(bd disk.BlockDevice) error {
func (vbm VolumeBitMap) Write(devicebytes []byte) error {
for i, bp := range vbm {
if err := disk.MarshalBlock(bd, bp); err != nil {
if err := disk.MarshalBlock(devicebytes, bp); err != nil {
return fmt.Errorf("cannot write block %d (device block %d) of Volume Bit Map: %v", i, bp.GetBlock(), err)
}
}
@ -351,14 +351,14 @@ type FileDescriptor struct {
HeaderPointer uint16 // Block number of the key block for the directory which describes this file.
}
// descriptor returns a disk.Descriptor for a FileDescriptor.
func (fd FileDescriptor) descriptor() disk.Descriptor {
desc := disk.Descriptor{
// descriptor returns a types.Descriptor for a FileDescriptor.
func (fd FileDescriptor) descriptor() types.Descriptor {
desc := types.Descriptor{
Name: fd.Name(),
Blocks: int(fd.BlocksUsed),
Length: int(fd.Eof[0]) + int(fd.Eof[1])<<8 + int(fd.Eof[2])<<16,
Locked: false, // TODO(zellyn): use prodos-style access in disk.Descriptor
Type: disk.Filetype(fd.FileType),
Locked: false, // TODO(zellyn): use prodos-style access in types.Descriptor
Type: types.Filetype(fd.FileType),
}
return desc
}
@ -652,18 +652,18 @@ func (v Volume) subdirDescriptors() []FileDescriptor {
// readVolume reads the entire volume and subdirectories from a device
// into memory.
func readVolume(bd disk.BlockDevice, keyBlock uint16) (Volume, error) {
func readVolume(devicebytes []byte, keyBlock uint16) (Volume, error) {
v := Volume{
keyBlock: &VolumeDirectoryKeyBlock{},
subdirsByBlock: make(map[uint16]*Subdirectory),
subdirsByName: make(map[string]*Subdirectory),
}
if err := disk.UnmarshalBlock(bd, v.keyBlock, keyBlock); err != nil {
if err := disk.UnmarshalBlock(devicebytes, v.keyBlock, keyBlock); err != nil {
return v, fmt.Errorf("cannot read first block of volume directory (block %d): %v", keyBlock, err)
}
if vbm, err := readVolumeBitMap(bd, v.keyBlock.Header.BitMapPointer); err != nil {
if vbm, err := readVolumeBitMap(devicebytes, v.keyBlock.Header.BitMapPointer); err != nil {
return v, err
} else {
v.bitmap = &vbm
@ -671,7 +671,7 @@ func readVolume(bd disk.BlockDevice, keyBlock uint16) (Volume, error) {
for block := v.keyBlock.Next; block != 0; block = v.blocks[len(v.blocks)-1].Next {
vdb := VolumeDirectoryBlock{}
if err := disk.UnmarshalBlock(bd, &vdb, block); err != nil {
if err := disk.UnmarshalBlock(devicebytes, &vdb, block); err != nil {
return v, err
}
v.blocks = append(v.blocks, &vdb)
@ -681,7 +681,7 @@ func readVolume(bd disk.BlockDevice, keyBlock uint16) (Volume, error) {
for i := 0; i < len(sdds); i++ {
sdd := sdds[i]
sub, err := readSubdirectory(bd, sdd)
sub, err := readSubdirectory(devicebytes, sdd)
if err != nil {
return v, err
}
@ -751,18 +751,18 @@ func parentDirName(parentDirectoryBlock uint16, keyBlock uint16, subdirMap map[u
// readSubdirectory reads a single subdirectory from a device into
// memory.
func readSubdirectory(bd disk.BlockDevice, fd FileDescriptor) (Subdirectory, error) {
func readSubdirectory(devicebytes []byte, fd FileDescriptor) (Subdirectory, error) {
s := Subdirectory{
keyBlock: &SubdirectoryKeyBlock{},
}
if err := disk.UnmarshalBlock(bd, s.keyBlock, fd.KeyPointer); err != nil {
if err := disk.UnmarshalBlock(devicebytes, s.keyBlock, fd.KeyPointer); err != nil {
return s, fmt.Errorf("cannot read first block of subdirectory %q (block %d): %v", fd.Name(), fd.KeyPointer, err)
}
for block := s.keyBlock.Next; block != 0; block = s.blocks[len(s.blocks)-1].Next {
sdb := SubdirectoryBlock{}
if err := disk.UnmarshalBlock(bd, &sdb, block); err != nil {
if err := disk.UnmarshalBlock(devicebytes, &sdb, block); err != nil {
return s, err
}
s.blocks = append(s.blocks, &sdb)
@ -783,10 +783,11 @@ func copyBytes(dst, src []byte) int {
// operator is a disk.Operator - an interface for performing
// high-level operations on files and directories.
type operator struct {
dev disk.BlockDevice
data []byte
debug bool
}
var _ disk.Operator = operator{}
var _ types.Operator = operator{}
// operatorName is the keyword name for the operator that undestands
// prodos disks/devices.
@ -797,11 +798,6 @@ 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 {
@ -810,14 +806,14 @@ func (o operator) HasSubdirs() bool {
// Catalog returns a catalog of disk entries. subdir should be empty
// for operating systems that do not support subdirectories.
func (o operator) Catalog(subdir string) ([]disk.Descriptor, error) {
func (o operator) Catalog(subdir string) ([]types.Descriptor, error) {
vol, err := readVolume(o.dev, 2)
vol, err := readVolume(o.data, 2)
if err != nil {
return nil, err
}
var result []disk.Descriptor
var result []types.Descriptor
if subdir == "" {
for _, desc := range vol.descriptors() {
@ -842,8 +838,8 @@ func (o operator) Catalog(subdir string) ([]disk.Descriptor, error) {
}
// GetFile retrieves a file by name.
func (o operator) GetFile(filename string) (disk.FileInfo, error) {
return disk.FileInfo{}, fmt.Errorf("%s doesn't implement GetFile yet", operatorName)
func (o operator) GetFile(filename string) (types.FileInfo, error) {
return types.FileInfo{}, fmt.Errorf("%s doesn't implement GetFile yet", operatorName)
}
// Delete deletes a file by name. It returns true if the file was
@ -855,37 +851,49 @@ func (o operator) Delete(filename string) (bool, error) {
// PutFile writes a file by name. If the file exists and overwrite
// is false, it returns with an error. Otherwise it returns true if
// an existing file was overwritten.
func (o operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool, err error) {
func (o operator) PutFile(fileInfo types.FileInfo, overwrite bool) (existed bool, err error) {
return false, fmt.Errorf("%s doesn't implement PutFile yet", operatorName)
}
// Write writes the underlying device blocks to the given writer.
func (o operator) Write(w io.Writer) (int, error) {
return o.dev.Write(w)
// OperatorFactory is a types.OperatorFactory for ProDos disks.
type OperatorFactory struct {
}
// deviceOperatorFactory is the factory that returns prodos operators
// given device images.
func deviceOperatorFactory(bd disk.BlockDevice) (disk.Operator, error) {
op := operator{dev: bd}
_, err := op.Catalog("")
if err != nil {
return nil, fmt.Errorf("Cannot read catalog. Underlying error: %v", err)
// Name returns the name of the operator.
func (of OperatorFactory) Name() string {
return operatorName
}
// SeemsToMatch returns true if the []byte disk image seems to match the
// system of this operator.
func (of OperatorFactory) SeemsToMatch(devicebytes []byte, debug bool) bool {
// For now, just return true if we can run Catalog successfully.
if len(devicebytes) == disk.FloppyDiskBytes {
swizzled, err := of.swizzle(devicebytes)
if err != nil {
return false
}
devicebytes = swizzled
}
return op, nil
}
// DiskOperatorFactory is the factory that returns dos3 operators
// given disk images.
func DiskOperatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
bd, err := disk.BlockDeviceFromSectorDisk(sd)
_, err := readVolume(devicebytes, 2)
if err != nil {
return nil, err
return false
}
return deviceOperatorFactory(bd)
return true
}
func init() {
disk.RegisterDeviceOperatorFactory(operatorName, deviceOperatorFactory)
disk.RegisterDiskOperatorFactory(operatorName, DiskOperatorFactory)
// Operator returns an Operator for the []byte disk image.
func (of OperatorFactory) Operator(devicebytes []byte, debug bool) (types.Operator, error) {
if len(devicebytes) == disk.FloppyDiskBytes {
swizzled, err := of.swizzle(devicebytes)
if err != nil {
return nil, err
}
devicebytes = swizzled
}
return operator{data: devicebytes, debug: debug}, nil
}
func (of OperatorFactory) swizzle(rawbytes []byte) ([]byte, error) {
return disk.Swizzle(rawbytes, disk.ProDosPhysicalToLogicalSectorMap)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/zellyn/diskii/disk"
"github.com/zellyn/diskii/errors"
"github.com/zellyn/diskii/types"
)
const (
@ -253,14 +254,14 @@ func decodeSymbol(five []byte, extra byte) string {
value := uint64(five[0]) + uint64(five[1])<<8 + uint64(five[2])<<16 + uint64(five[3])<<24 + uint64(five[4])<<32 + uint64(extra)<<40
for value&0x1f > 0 {
if value&0x1f < 27 {
result = result + string(value&0x1f+'@')
result = result + string(rune(value&0x1f+'@'))
value >>= 5
continue
}
if value&0x20 == 0 {
result = result + string((value&0x1f)-0x1b+'0')
result = result + string(rune((value&0x1f)-0x1b+'0'))
} else {
result = result + string((value&0x1f)-0x1b+'5')
result = result + string(rune((value&0x1f)-0x1b+'5'))
}
value >>= 6
}
@ -639,74 +640,75 @@ func (st SymbolTable) FilesForCompoundName(filename string) (numFile byte, named
return numFile, namedFile, parts[1], nil
}
// Operator is a disk.Operator - an interface for performing
// operator is a disk.Operator - an interface for performing
// high-level operations on files and directories.
type Operator struct {
data []byte
SM SectorMap
ST SymbolTable
type operator struct {
data []byte
SM SectorMap
ST SymbolTable
debug bool
}
var _ disk.Operator = Operator{}
var _ types.Operator = operator{}
// operatorName is the keyword name for the operator that undestands
// NakedOS/Super-Mon disks.
const operatorName = "nakedos"
// Name returns the name of the Operator.
func (o Operator) Name() string {
func (o operator) Name() string {
return operatorName
}
// HasSubdirs returns true if the underlying operating system on the
// disk allows subdirectories.
func (o Operator) HasSubdirs() bool {
func (o operator) HasSubdirs() bool {
return false
}
// Catalog returns a catalog of disk entries. subdir should be empty
// for operating systems that do not support subdirectories.
func (o Operator) Catalog(subdir string) ([]disk.Descriptor, error) {
var descs []disk.Descriptor
func (o operator) Catalog(subdir string) ([]types.Descriptor, error) {
var descs []types.Descriptor
sectorsByFile := o.SM.SectorsByFile()
for file := byte(1); file < FileReserved; file++ {
l := len(sectorsByFile[file])
if l == 0 {
continue
}
descs = append(descs, disk.Descriptor{
descs = append(descs, types.Descriptor{
Name: NameForFile(file, o.ST),
Fullname: FullnameForFile(file, o.ST),
Sectors: l,
Length: l * 256,
Locked: false,
Type: disk.FiletypeBinary,
Type: types.FiletypeBinary,
})
}
return descs, nil
}
// GetFile retrieves a file by name.
func (o Operator) GetFile(filename string) (disk.FileInfo, error) {
func (o operator) GetFile(filename string) (types.FileInfo, error) {
file, err := o.ST.FileForName(filename)
if err != nil {
return disk.FileInfo{}, err
return types.FileInfo{}, err
}
data, err := o.SM.ReadFile(o.data, file)
if err != nil {
return disk.FileInfo{}, fmt.Errorf("error reading file DF%02x: %v", file, err)
return types.FileInfo{}, fmt.Errorf("error reading file DF%02x: %v", file, err)
}
if len(data) == 0 {
return disk.FileInfo{}, fmt.Errorf("file DF%02x not fount", file)
return types.FileInfo{}, fmt.Errorf("file DF%02x not fount", file)
}
desc := disk.Descriptor{
desc := types.Descriptor{
Name: NameForFile(file, o.ST),
Sectors: len(data) / 256,
Length: len(data),
Locked: false,
Type: disk.FiletypeBinary,
Type: types.FiletypeBinary,
}
fi := disk.FileInfo{
fi := types.FileInfo{
Descriptor: desc,
Data: data,
}
@ -718,7 +720,7 @@ func (o Operator) GetFile(filename string) (disk.FileInfo, error) {
// Delete deletes a file by name. It returns true if the file was
// deleted, false if it didn't exist.
func (o Operator) Delete(filename string) (bool, error) {
func (o operator) Delete(filename string) (bool, error) {
file, err := o.ST.FileForName(filename)
if err != nil {
return false, err
@ -742,8 +744,8 @@ func (o Operator) Delete(filename string) (bool, error) {
// PutFile writes a file by name. If the file exists and overwrite
// is false, it returns with an error. Otherwise it returns true if
// an existing file was overwritten.
func (o Operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool, err error) {
if fileInfo.Descriptor.Type != disk.FiletypeBinary {
func (o operator) PutFile(fileInfo types.FileInfo, overwrite bool) (existed bool, err error) {
if fileInfo.Descriptor.Type != types.FiletypeBinary {
return false, fmt.Errorf("%s: only binary file type supported", operatorName)
}
if fileInfo.Descriptor.Length != len(fileInfo.Data) {
@ -783,9 +785,9 @@ func (o Operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool,
return existed, nil
}
// OperatorFactory is the factory that returns supermon operators
// XOperatorFactory is the factory that returns supermon operators
// given disk images.
func OperatorFactory(diskbytes []byte) (disk.Operator, error) {
func XOperatorFactory(diskbytes []byte) (types.Operator, error) {
sm, err := LoadSectorMap(diskbytes)
if err != nil {
return nil, err
@ -794,7 +796,50 @@ func OperatorFactory(diskbytes []byte) (disk.Operator, error) {
return nil, err
}
op := Operator{data: diskbytes, SM: sm}
op := operator{data: diskbytes, SM: sm}
st, err := sm.ReadSymbolTable(diskbytes)
if err == nil {
op.ST = st
}
return op, nil
}
// OperatorFactory is a types.OperatorFactory for DOS 3.3 disks.
type OperatorFactory struct {
}
// Name returns the name of the operator.
func (of OperatorFactory) Name() string {
return operatorName
}
// SeemsToMatch returns true if the []byte disk image seems to match the
// system of this operator.
func (of OperatorFactory) SeemsToMatch(diskbytes []byte, debug bool) bool {
// For now, just return true if we can run Catalog successfully.
sm, err := LoadSectorMap(diskbytes)
if err != nil {
return false
}
if err := sm.Verify(); err != nil {
return false
}
return true
}
// Operator returns an Operator for the []byte disk image.
func (of OperatorFactory) Operator(diskbytes []byte, debug bool) (types.Operator, error) {
sm, err := LoadSectorMap(diskbytes)
if err != nil {
return nil, err
}
if err := sm.Verify(); err != nil {
return nil, err
}
op := operator{data: diskbytes, SM: sm, debug: debug}
st, err := sm.ReadSymbolTable(diskbytes)
if err == nil {

View File

@ -3,12 +3,14 @@
package supermon
import (
"os"
"reflect"
"strings"
"testing"
"github.com/kr/pretty"
"github.com/zellyn/diskii/disk"
"github.com/zellyn/diskii/types"
)
const testDisk = "testdata/chacha20.dsk"
@ -27,16 +29,36 @@ No more; and by a sleep, to say we end
// loadSectorMap loads a sector map for the disk image contained in
// filename. It returns the sector map and a sector disk.
func loadSectorMap(filename string) (SectorMap, disk.SectorDisk, error) {
sd, err := disk.LoadDSK(filename)
func loadSectorMap(filename string) (SectorMap, []byte, error) {
rawbytes, err := os.ReadFile(filename)
if err != nil {
return nil, nil, err
}
sm, err := LoadSectorMap(sd)
diskbytes, err := disk.Swizzle(rawbytes, disk.Dos33LogicalToPhysicalSectorMap)
if err != nil {
return nil, nil, err
}
return sm, sd, nil
sm, err := LoadSectorMap(diskbytes)
if err != nil {
return nil, nil, err
}
return sm, diskbytes, nil
}
// 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)
if err != nil {
return nil, err
}
op, _, err := disk.OpenImage(f, "do", "nakedos", &types.Globals{
DiskOperatorFactories: []types.OperatorFactory{OperatorFactory{}},
})
if err != nil {
return nil, err
}
return op, nil
}
// TestReadSectorMap tests the reading of the sector map of a test
@ -126,11 +148,7 @@ func TestReadSymbolTable(t *testing.T) {
// TestGetFile tests the retrieval of a file's contents, using the
// Operator interface.
func TestGetFile(t *testing.T) {
sd, err := disk.OpenDisk(testDisk)
if err != nil {
t.Fatal(err)
}
op, err := disk.OperatorForDisk(sd)
op, err := getOperator(testDisk)
if err != nil {
t.Fatal(err)
}
@ -213,20 +231,16 @@ func TestReadWriteSymbolTable(t *testing.T) {
// TestPutFile tests the creation of a file, using the Operator
// interface.
func TestPutFile(t *testing.T) {
sd, err := disk.OpenDisk(testDisk)
if err != nil {
t.Fatal(err)
}
op, err := disk.OperatorForDisk(sd)
op, err := getOperator(testDisk)
if err != nil {
t.Fatal(err)
}
contents := []byte(cities)
fileInfo := disk.FileInfo{
Descriptor: disk.Descriptor{
fileInfo := types.FileInfo{
Descriptor: types.Descriptor{
Name: "FNEWFILE",
Length: len(contents),
Type: disk.FiletypeBinary,
Type: types.FiletypeBinary,
},
Data: contents,
}

View File

@ -2,9 +2,7 @@ package types
// Globals holds flags and configuration that are shared globally.
type Globals struct {
Debug bool
Order string //Logical-to-physical sector order
System string // DOS system used for image
Debug bool
DiskOperatorFactories []OperatorFactory
}

View File

@ -9,8 +9,7 @@ import (
)
func TestBasicLoad(t *testing.T) {
bb := data.MustAsset("data/disks/dos33master.woz")
wz, err := woz.Decode(bytes.NewReader(bb))
wz, err := woz.Decode(bytes.NewReader(data.DOS33master_woz))
if err != nil {
t.Fatal(err)
}