mirror of
https://github.com/zellyn/diskii.git
synced 2025-02-17 16:30:44 +00:00
catalog working for all types, reorder added
This commit is contained in:
parent
9c66e2c5e6
commit
ef9115dcaf
57
README.md
57
README.md
@ -47,8 +47,8 @@ Current disk operations supported:
|
|||||||
| ---------------- | -------- | ------ | ------------------ |
|
| ---------------- | -------- | ------ | ------------------ |
|
||||||
| basic structures | ✓ | ✓ | ✓ |
|
| basic structures | ✓ | ✓ | ✓ |
|
||||||
| ls | ✓ | ✓ | ✓ |
|
| ls | ✓ | ✓ | ✓ |
|
||||||
| dump | ✓ | ✗ | ✓ |
|
| dump | ✗ | ✗ | ✗ |
|
||||||
| put | ✗ | ✗ | ✓ |
|
| put | ✗ | ✗ | ✗ |
|
||||||
| dumptext | ✗ | ✗ | ✗ |
|
| dumptext | ✗ | ✗ | ✗ |
|
||||||
| delete | ✗ | ✗ | ✗ |
|
| delete | ✗ | ✗ | ✗ |
|
||||||
| rename | ✗ | ✗ | ✗ |
|
| rename | ✗ | ✗ | ✗ |
|
||||||
@ -78,12 +78,9 @@ will be likely to get priority.
|
|||||||
- [x] Implement `GetFile` for DOS 3.3
|
- [x] Implement `GetFile` for DOS 3.3
|
||||||
- [ ] Add and implement the `-l` flag for `ls`
|
- [ ] Add and implement the `-l` flag for `ls`
|
||||||
- [x] Add `Delete` to the `disk.Operator` interface
|
- [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
|
- [ ] Implement it for DOS 3.3
|
||||||
- [ ] Make 13-sector DOS disks work
|
- [x] Add basic ProDOS structures
|
||||||
- [ ] Read/write nybble formats
|
|
||||||
- [ ] Read/write gzipped files
|
|
||||||
- [ ] Add basic ProDOS structures
|
|
||||||
- [ ] Add ProDOS support
|
- [ ] Add ProDOS support
|
||||||
|
|
||||||
# Related tools
|
# Related tools
|
||||||
@ -114,38 +111,34 @@ will be likely to get priority.
|
|||||||
|
|
||||||
- `.do`
|
- `.do`
|
||||||
- `.po`
|
- `.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.
|
| Physical Sectors | DOS 3.2 Logical | DOS 3.3 Logical | ProDOS/Pascal Logical | CP/M Logical |
|
||||||
|
|------------------|-----------------|-----------------|-----------------------|------------- |
|
||||||
DOS 3.3+: the 16 physical sectors are stored in ascending order on disk, not physically skewed at all. The
|
| 0 | 0 | 0 | 0.0 | 0.0 |
|
||||||
|
| 1 | 1 | 7 | 4.0 | 2.3 |
|
||||||
|
| 2 | 2 | E | 0.1 | 1.2 |
|
||||||
| Logical Sector | DOS 3.3 Physical Sector | ProDOS Physical Sector |
|
| 3 | 3 | 6 | 4.1 | 0.1 |
|
||||||
| --------------- | -------------- | ------------- |
|
| 4 | 4 | D | 1.0 | 3.0 |
|
||||||
| 0 | 0 | x |
|
| 5 | 5 | 5 | 5.0 | 1.3 |
|
||||||
| 1 | D | x |
|
| 6 | 6 | C | 1.1 | 0.2 |
|
||||||
| 2 | B | x |
|
| 7 | 7 | 4 | 5.1 | 3.1 |
|
||||||
| 3 | 9 | x |
|
| 8 | 8 | B | 2.0 | 2.0 |
|
||||||
| 4 | 7 | x |
|
| 9 | 9 | 3 | 6.0 | 0.3 |
|
||||||
| 5 | 5 | x |
|
| A | A | A | 2.1 | 3.2 |
|
||||||
| 6 | 3 | x |
|
| B | B | 2 | 6.1 | 2.1 |
|
||||||
| 7 | 1 | x |
|
| C | C | 9 | 3.0 | 1.0 |
|
||||||
| 8 | E | x |
|
| D | | 1 | 7.0 | 3.3 |
|
||||||
| 9 | C | x |
|
| E | | 8 | 3.1 | 2.2 |
|
||||||
| A | A | x |
|
| F | | F | 7.1 | 1.1 |
|
||||||
| B | 8 | x |
|
|
||||||
| C | 6 | x |
|
|
||||||
| D | 4 | x |
|
|
||||||
| E | 2 | x |
|
|
||||||
| F | F | x |
|
|
||||||
|
|
||||||
|
_Note: DOS 3.2 rearranged the physical sectors on disk to achieve interleaving._
|
||||||
### RWTS - DOS
|
### RWTS - DOS
|
||||||
|
|
||||||
Sector mapping:
|
Sector mapping:
|
||||||
http://www.textfiles.com/apple/ANATOMY/rwts.s.txt and search for INTRLEAV
|
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`
|
`00 0D 0B 09 07 05 03 01 0E 0C 0A 08 06 04 02 0F`
|
||||||
|
|
||||||
|
@ -10,19 +10,19 @@ import (
|
|||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var shortnames bool // flag for whether to print short filenames
|
|
||||||
var debug bool
|
|
||||||
|
|
||||||
type LsCmd struct {
|
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).'"`
|
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.'"`
|
Image *os.File `kong:"arg,required,help='Disk/device image to read.'"`
|
||||||
Directory string `kong:"arg,optional,help='Directory to list (ProDOS only).'"`
|
Directory string `kong:"arg,optional,help='Directory to list (ProDOS only).'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LsCmd) Run(globals *types.Globals) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("%w: %s", err, l.Image.Name())
|
||||||
}
|
}
|
||||||
if globals.Debug {
|
if globals.Debug {
|
||||||
fmt.Fprintf(os.Stderr, "Opened disk with order %q, system %q\n", order, op.Name())
|
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
|
return err
|
||||||
}
|
}
|
||||||
for _, fd := range fds {
|
for _, fd := range fds {
|
||||||
if !shortnames && fd.Fullname != "" {
|
if !l.ShortNames && fd.Fullname != "" {
|
||||||
fmt.Println(fd.Fullname)
|
fmt.Println(fd.Fullname)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(fd.Name)
|
fmt.Println(fd.Name)
|
||||||
|
101
cmd/reorder.go
Normal file
101
cmd/reorder.go
Normal 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 ""
|
||||||
|
}
|
||||||
|
}
|
12
data/data.go
12
data/data.go
@ -3,16 +3,20 @@ package data
|
|||||||
import _ "embed"
|
import _ "embed"
|
||||||
|
|
||||||
// DOS 3.3 Master Disk.
|
// DOS 3.3 Master Disk.
|
||||||
//go:embed disks/dos33mst.dsk
|
//go:embed disks/dos33master.dsk
|
||||||
var DOS33master_dsk []byte
|
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.
|
// John Brooks' update to ProDOS.
|
||||||
// Website: https://prodos8.com
|
// Website: https://prodos8.com
|
||||||
// Announcements: https://www.callapple.org/author/jbrooks/
|
// Announcements: https://www.callapple.org/author/jbrooks/
|
||||||
//go:embed disks/ProDOS_2_4_2.dsk
|
//go:embed disks/ProDOS_2_4_2.po
|
||||||
var ProDOS242_dsk []byte
|
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
|
//go:embed boot/prodos-new-boot0.bin
|
||||||
var ProDOSNewBootSector0 []byte
|
var ProDOSNewBootSector0 []byte
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
BIN
data/disks/ProDOS_2_4_2.po
Normal file
BIN
data/disks/ProDOS_2_4_2.po
Normal file
Binary file not shown.
Binary file not shown.
@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// A ProDOS block.
|
// A ProDOS block.
|
||||||
type Block [256]byte
|
type Block [512]byte
|
||||||
|
|
||||||
// Dev represents a .po disk image.
|
// Dev represents a .po disk image.
|
||||||
type Dev struct {
|
type Dev struct {
|
||||||
|
14
disk/disk.go
14
disk/disk.go
@ -41,6 +41,20 @@ var ProDosPhysicalToLogicalSectorMap = []int{
|
|||||||
0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F,
|
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.
|
// TrackSector is a pair of track/sector bytes.
|
||||||
type TrackSector struct {
|
type TrackSector struct {
|
||||||
Track byte
|
Track byte
|
||||||
|
30
disk/open.go
30
disk/open.go
@ -10,36 +10,30 @@ import (
|
|||||||
"github.com/zellyn/diskii/types"
|
"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.
|
// 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)
|
bb, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
if len(bb) == FloppyDiskBytes {
|
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")
|
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
|
var factories []types.OperatorFactory
|
||||||
for _, factory := range globals.DiskOperatorFactories {
|
for _, factory := range globals.DiskOperatorFactories {
|
||||||
if globals.System == "auto" || globals.System == factory.Name() {
|
if system == "auto" || system == factory.Name() {
|
||||||
factories = append(factories, factory)
|
factories = append(factories, factory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(factories) == 0 {
|
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}
|
orders := []string{order}
|
||||||
switch globals.Order {
|
switch order {
|
||||||
case "do", "po":
|
case "do", "po":
|
||||||
// nothing more
|
// nothing more
|
||||||
case "auto":
|
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)
|
return nil, "", fmt.Errorf("unknown disk image extension: %q", ext)
|
||||||
}
|
}
|
||||||
default:
|
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 {
|
for _, order := range orders {
|
||||||
swizzled, err := Swizzle(diskbytes, diskOrdersByName[order])
|
swizzled, err := Swizzle(diskbytes, LogicalToPhysicalByName[order])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
for _, factory := range factories {
|
for _, factory := range factories {
|
||||||
if len(orders) == 1 && globals.System != "auto" {
|
if len(orders) == 1 && system != "auto" {
|
||||||
if globals.Debug {
|
if globals.Debug {
|
||||||
fmt.Fprintf(os.Stderr, "Attempting to open with order=%s, system=%s.\n", order, factory.Name())
|
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
|
// Swizzle changes the sector ordering according to the order parameter. If
|
||||||
// order is nil, it leaves the order unchanged.
|
// order is nil, it leaves the order unchanged.
|
||||||
func Swizzle(diskimage []byte, order []int) ([]byte, error) {
|
func Swizzle(diskimage []byte, order []int) ([]byte, error) {
|
||||||
if len(diskimage) != FloppyDiskBytes {
|
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 {
|
if err := validateOrder(order); err != nil {
|
||||||
return nil, fmt.Errorf("called Swizzle with weird order: %w", err)
|
return nil, fmt.Errorf("called Swizzle with weird order: %w", err)
|
||||||
|
24
dos3/dos3.go
24
dos3/dos3.go
@ -7,7 +7,6 @@ package dos3
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zellyn/diskii/disk"
|
"github.com/zellyn/diskii/disk"
|
||||||
@ -500,9 +499,6 @@ func readCatalogSectors(diskbytes []byte, debug bool) ([]CatalogSector, error) {
|
|||||||
if err := v.Validate(); err != nil {
|
if err := v.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("Invalid VTOC sector: %v", err)
|
return nil, fmt.Errorf("Invalid VTOC sector: %v", err)
|
||||||
}
|
}
|
||||||
if debug {
|
|
||||||
fmt.Fprintf(os.Stderr, "Read VTOC sector: %#v\n", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
nextTrack := v.CatalogTrack
|
nextTrack := v.CatalogTrack
|
||||||
nextSector := v.CatalogSector
|
nextSector := v.CatalogSector
|
||||||
@ -686,9 +682,13 @@ func (of OperatorFactory) Name() string {
|
|||||||
|
|
||||||
// SeemsToMatch returns true if the []byte disk image seems to match the
|
// SeemsToMatch returns true if the []byte disk image seems to match the
|
||||||
// system of this operator.
|
// 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.
|
// 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 {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -696,6 +696,14 @@ func (of OperatorFactory) SeemsToMatch(diskbytes []byte, debug bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Operator returns an Operator for the []byte disk image.
|
// Operator returns an Operator for the []byte disk image.
|
||||||
func (of OperatorFactory) Operator(diskbytes []byte, debug bool) (types.Operator, error) {
|
func (of OperatorFactory) Operator(rawbytes []byte, debug bool) (types.Operator, error) {
|
||||||
return operator{data: diskbytes, debug: debug}, nil
|
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)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,7 +16,23 @@ import (
|
|||||||
// is "-", in which case it reads from stdin.
|
// is "-", in which case it reads from stdin.
|
||||||
func FileContentsOrStdIn(s string) ([]byte, error) {
|
func FileContentsOrStdIn(s string) ([]byte, error) {
|
||||||
if s == "-" {
|
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
17
main.go
@ -5,6 +5,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"github.com/zellyn/diskii/cmd"
|
"github.com/zellyn/diskii/cmd"
|
||||||
"github.com/zellyn/diskii/dos3"
|
"github.com/zellyn/diskii/dos3"
|
||||||
|
"github.com/zellyn/diskii/prodos"
|
||||||
|
"github.com/zellyn/diskii/supermon"
|
||||||
"github.com/zellyn/diskii/types"
|
"github.com/zellyn/diskii/types"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -14,11 +16,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var cli struct {
|
var cli struct {
|
||||||
Debug bool `kong:"short='v',help='Enable debug mode.'"`
|
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.'"`
|
|
||||||
|
|
||||||
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 {
|
func run() error {
|
||||||
@ -34,13 +35,11 @@ func run() error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
globals := &types.Globals{
|
globals := &types.Globals{
|
||||||
Debug: cli.Debug,
|
Debug: cli.Debug,
|
||||||
Order: cli.Order,
|
|
||||||
System: cli.System,
|
|
||||||
DiskOperatorFactories: []types.OperatorFactory{
|
DiskOperatorFactories: []types.OperatorFactory{
|
||||||
dos3.OperatorFactory{},
|
dos3.OperatorFactory{},
|
||||||
// supermon.OperatorFactory,
|
supermon.OperatorFactory{},
|
||||||
// prodos.DiskOperatorFactory,
|
prodos.OperatorFactory{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Call the Run() method of the selected parsed command.
|
// Call the Run() method of the selected parsed command.
|
||||||
|
@ -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\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 -
|
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]]
|
||||||
|
116
prodos/prodos.go
116
prodos/prodos.go
@ -7,9 +7,9 @@ package prodos
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/zellyn/diskii/disk"
|
"github.com/zellyn/diskii/disk"
|
||||||
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage types.
|
// Storage types.
|
||||||
@ -104,11 +104,11 @@ func (vbm VolumeBitMap) IsFree(block uint16) bool {
|
|||||||
|
|
||||||
// readVolumeBitMap reads the entire volume bitmap from a block
|
// readVolumeBitMap reads the entire volume bitmap from a block
|
||||||
// device.
|
// device.
|
||||||
func readVolumeBitMap(bd disk.BlockDevice, startBlock uint16) (VolumeBitMap, error) {
|
func readVolumeBitMap(devicebytes []byte, startBlock uint16) (VolumeBitMap, error) {
|
||||||
blocks := bd.Blocks() / 4096
|
blocks := uint16(len(devicebytes) / 512 / 4096)
|
||||||
vbm := NewVolumeBitMap(startBlock, blocks)
|
vbm := NewVolumeBitMap(startBlock, blocks)
|
||||||
for i := 0; i < len(vbm); i++ {
|
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)
|
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.
|
// 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 {
|
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)
|
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.
|
HeaderPointer uint16 // Block number of the key block for the directory which describes this file.
|
||||||
}
|
}
|
||||||
|
|
||||||
// descriptor returns a disk.Descriptor for a FileDescriptor.
|
// descriptor returns a types.Descriptor for a FileDescriptor.
|
||||||
func (fd FileDescriptor) descriptor() disk.Descriptor {
|
func (fd FileDescriptor) descriptor() types.Descriptor {
|
||||||
desc := disk.Descriptor{
|
desc := types.Descriptor{
|
||||||
Name: fd.Name(),
|
Name: fd.Name(),
|
||||||
Blocks: int(fd.BlocksUsed),
|
Blocks: int(fd.BlocksUsed),
|
||||||
Length: int(fd.Eof[0]) + int(fd.Eof[1])<<8 + int(fd.Eof[2])<<16,
|
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
|
Locked: false, // TODO(zellyn): use prodos-style access in types.Descriptor
|
||||||
Type: disk.Filetype(fd.FileType),
|
Type: types.Filetype(fd.FileType),
|
||||||
}
|
}
|
||||||
return desc
|
return desc
|
||||||
}
|
}
|
||||||
@ -652,18 +652,18 @@ func (v Volume) subdirDescriptors() []FileDescriptor {
|
|||||||
|
|
||||||
// readVolume reads the entire volume and subdirectories from a device
|
// readVolume reads the entire volume and subdirectories from a device
|
||||||
// into memory.
|
// into memory.
|
||||||
func readVolume(bd disk.BlockDevice, keyBlock uint16) (Volume, error) {
|
func readVolume(devicebytes []byte, keyBlock uint16) (Volume, error) {
|
||||||
v := Volume{
|
v := Volume{
|
||||||
keyBlock: &VolumeDirectoryKeyBlock{},
|
keyBlock: &VolumeDirectoryKeyBlock{},
|
||||||
subdirsByBlock: make(map[uint16]*Subdirectory),
|
subdirsByBlock: make(map[uint16]*Subdirectory),
|
||||||
subdirsByName: make(map[string]*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)
|
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
|
return v, err
|
||||||
} else {
|
} else {
|
||||||
v.bitmap = &vbm
|
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 {
|
for block := v.keyBlock.Next; block != 0; block = v.blocks[len(v.blocks)-1].Next {
|
||||||
vdb := VolumeDirectoryBlock{}
|
vdb := VolumeDirectoryBlock{}
|
||||||
if err := disk.UnmarshalBlock(bd, &vdb, block); err != nil {
|
if err := disk.UnmarshalBlock(devicebytes, &vdb, block); err != nil {
|
||||||
return v, err
|
return v, err
|
||||||
}
|
}
|
||||||
v.blocks = append(v.blocks, &vdb)
|
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++ {
|
for i := 0; i < len(sdds); i++ {
|
||||||
sdd := sdds[i]
|
sdd := sdds[i]
|
||||||
sub, err := readSubdirectory(bd, sdd)
|
sub, err := readSubdirectory(devicebytes, sdd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return v, err
|
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
|
// readSubdirectory reads a single subdirectory from a device into
|
||||||
// memory.
|
// memory.
|
||||||
func readSubdirectory(bd disk.BlockDevice, fd FileDescriptor) (Subdirectory, error) {
|
func readSubdirectory(devicebytes []byte, fd FileDescriptor) (Subdirectory, error) {
|
||||||
s := Subdirectory{
|
s := Subdirectory{
|
||||||
keyBlock: &SubdirectoryKeyBlock{},
|
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)
|
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 {
|
for block := s.keyBlock.Next; block != 0; block = s.blocks[len(s.blocks)-1].Next {
|
||||||
sdb := SubdirectoryBlock{}
|
sdb := SubdirectoryBlock{}
|
||||||
if err := disk.UnmarshalBlock(bd, &sdb, block); err != nil {
|
if err := disk.UnmarshalBlock(devicebytes, &sdb, block); err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
s.blocks = append(s.blocks, &sdb)
|
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
|
// operator is a disk.Operator - an interface for performing
|
||||||
// high-level operations on files and directories.
|
// high-level operations on files and directories.
|
||||||
type operator struct {
|
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
|
// operatorName is the keyword name for the operator that undestands
|
||||||
// prodos disks/devices.
|
// prodos disks/devices.
|
||||||
@ -797,11 +798,6 @@ func (o operator) Name() string {
|
|||||||
return operatorName
|
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
|
// HasSubdirs returns true if the underlying operating system on the
|
||||||
// disk allows subdirectories.
|
// disk allows subdirectories.
|
||||||
func (o operator) HasSubdirs() bool {
|
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
|
// Catalog returns a catalog of disk entries. subdir should be empty
|
||||||
// for operating systems that do not support subdirectories.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []disk.Descriptor
|
var result []types.Descriptor
|
||||||
|
|
||||||
if subdir == "" {
|
if subdir == "" {
|
||||||
for _, desc := range vol.descriptors() {
|
for _, desc := range vol.descriptors() {
|
||||||
@ -842,8 +838,8 @@ func (o operator) Catalog(subdir string) ([]disk.Descriptor, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFile retrieves a file by name.
|
// GetFile retrieves a file by name.
|
||||||
func (o operator) GetFile(filename string) (disk.FileInfo, error) {
|
func (o operator) GetFile(filename string) (types.FileInfo, error) {
|
||||||
return disk.FileInfo{}, fmt.Errorf("%s doesn't implement GetFile yet", operatorName)
|
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
|
// 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
|
// PutFile writes a file by name. If the file exists and overwrite
|
||||||
// is false, it returns with an error. Otherwise it returns true if
|
// is false, it returns with an error. Otherwise it returns true if
|
||||||
// an existing file was overwritten.
|
// 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)
|
return false, fmt.Errorf("%s doesn't implement PutFile yet", operatorName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the underlying device blocks to the given writer.
|
// OperatorFactory is a types.OperatorFactory for ProDos disks.
|
||||||
func (o operator) Write(w io.Writer) (int, error) {
|
type OperatorFactory struct {
|
||||||
return o.dev.Write(w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// deviceOperatorFactory is the factory that returns prodos operators
|
// Name returns the name of the operator.
|
||||||
// given device images.
|
func (of OperatorFactory) Name() string {
|
||||||
func deviceOperatorFactory(bd disk.BlockDevice) (disk.Operator, error) {
|
return operatorName
|
||||||
op := operator{dev: bd}
|
}
|
||||||
_, err := op.Catalog("")
|
|
||||||
if err != nil {
|
// SeemsToMatch returns true if the []byte disk image seems to match the
|
||||||
return nil, fmt.Errorf("Cannot read catalog. Underlying error: %v", err)
|
// 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
|
_, err := readVolume(devicebytes, 2)
|
||||||
}
|
|
||||||
|
|
||||||
// DiskOperatorFactory is the factory that returns dos3 operators
|
|
||||||
// given disk images.
|
|
||||||
func DiskOperatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
|
||||||
bd, err := disk.BlockDeviceFromSectorDisk(sd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return false
|
||||||
}
|
}
|
||||||
return deviceOperatorFactory(bd)
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// Operator returns an Operator for the []byte disk image.
|
||||||
disk.RegisterDeviceOperatorFactory(operatorName, deviceOperatorFactory)
|
func (of OperatorFactory) Operator(devicebytes []byte, debug bool) (types.Operator, error) {
|
||||||
disk.RegisterDiskOperatorFactory(operatorName, DiskOperatorFactory)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/zellyn/diskii/disk"
|
"github.com/zellyn/diskii/disk"
|
||||||
"github.com/zellyn/diskii/errors"
|
"github.com/zellyn/diskii/errors"
|
||||||
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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
|
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 {
|
for value&0x1f > 0 {
|
||||||
if value&0x1f < 27 {
|
if value&0x1f < 27 {
|
||||||
result = result + string(value&0x1f+'@')
|
result = result + string(rune(value&0x1f+'@'))
|
||||||
value >>= 5
|
value >>= 5
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if value&0x20 == 0 {
|
if value&0x20 == 0 {
|
||||||
result = result + string((value&0x1f)-0x1b+'0')
|
result = result + string(rune((value&0x1f)-0x1b+'0'))
|
||||||
} else {
|
} else {
|
||||||
result = result + string((value&0x1f)-0x1b+'5')
|
result = result + string(rune((value&0x1f)-0x1b+'5'))
|
||||||
}
|
}
|
||||||
value >>= 6
|
value >>= 6
|
||||||
}
|
}
|
||||||
@ -639,74 +640,75 @@ func (st SymbolTable) FilesForCompoundName(filename string) (numFile byte, named
|
|||||||
return numFile, namedFile, parts[1], nil
|
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.
|
// high-level operations on files and directories.
|
||||||
type Operator struct {
|
type operator struct {
|
||||||
data []byte
|
data []byte
|
||||||
SM SectorMap
|
SM SectorMap
|
||||||
ST SymbolTable
|
ST SymbolTable
|
||||||
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ disk.Operator = Operator{}
|
var _ types.Operator = operator{}
|
||||||
|
|
||||||
// operatorName is the keyword name for the operator that undestands
|
// operatorName is the keyword name for the operator that undestands
|
||||||
// NakedOS/Super-Mon disks.
|
// NakedOS/Super-Mon disks.
|
||||||
const operatorName = "nakedos"
|
const operatorName = "nakedos"
|
||||||
|
|
||||||
// Name returns the name of the Operator.
|
// Name returns the name of the Operator.
|
||||||
func (o Operator) Name() string {
|
func (o operator) Name() string {
|
||||||
return operatorName
|
return operatorName
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasSubdirs returns true if the underlying operating system on the
|
// HasSubdirs returns true if the underlying operating system on the
|
||||||
// disk allows subdirectories.
|
// disk allows subdirectories.
|
||||||
func (o Operator) HasSubdirs() bool {
|
func (o operator) HasSubdirs() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catalog returns a catalog of disk entries. subdir should be empty
|
// Catalog returns a catalog of disk entries. subdir should be empty
|
||||||
// for operating systems that do not support subdirectories.
|
// 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) {
|
||||||
var descs []disk.Descriptor
|
var descs []types.Descriptor
|
||||||
sectorsByFile := o.SM.SectorsByFile()
|
sectorsByFile := o.SM.SectorsByFile()
|
||||||
for file := byte(1); file < FileReserved; file++ {
|
for file := byte(1); file < FileReserved; file++ {
|
||||||
l := len(sectorsByFile[file])
|
l := len(sectorsByFile[file])
|
||||||
if l == 0 {
|
if l == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
descs = append(descs, disk.Descriptor{
|
descs = append(descs, types.Descriptor{
|
||||||
Name: NameForFile(file, o.ST),
|
Name: NameForFile(file, o.ST),
|
||||||
Fullname: FullnameForFile(file, o.ST),
|
Fullname: FullnameForFile(file, o.ST),
|
||||||
Sectors: l,
|
Sectors: l,
|
||||||
Length: l * 256,
|
Length: l * 256,
|
||||||
Locked: false,
|
Locked: false,
|
||||||
Type: disk.FiletypeBinary,
|
Type: types.FiletypeBinary,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return descs, nil
|
return descs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFile retrieves a file by name.
|
// 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)
|
file, err := o.ST.FileForName(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return disk.FileInfo{}, err
|
return types.FileInfo{}, err
|
||||||
}
|
}
|
||||||
data, err := o.SM.ReadFile(o.data, file)
|
data, err := o.SM.ReadFile(o.data, file)
|
||||||
if err != nil {
|
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 {
|
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),
|
Name: NameForFile(file, o.ST),
|
||||||
Sectors: len(data) / 256,
|
Sectors: len(data) / 256,
|
||||||
Length: len(data),
|
Length: len(data),
|
||||||
Locked: false,
|
Locked: false,
|
||||||
Type: disk.FiletypeBinary,
|
Type: types.FiletypeBinary,
|
||||||
}
|
}
|
||||||
fi := disk.FileInfo{
|
fi := types.FileInfo{
|
||||||
Descriptor: desc,
|
Descriptor: desc,
|
||||||
Data: data,
|
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
|
// Delete deletes a file by name. It returns true if the file was
|
||||||
// deleted, false if it didn't exist.
|
// 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)
|
file, err := o.ST.FileForName(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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
|
// PutFile writes a file by name. If the file exists and overwrite
|
||||||
// is false, it returns with an error. Otherwise it returns true if
|
// is false, it returns with an error. Otherwise it returns true if
|
||||||
// an existing file was overwritten.
|
// 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) {
|
||||||
if fileInfo.Descriptor.Type != disk.FiletypeBinary {
|
if fileInfo.Descriptor.Type != types.FiletypeBinary {
|
||||||
return false, fmt.Errorf("%s: only binary file type supported", operatorName)
|
return false, fmt.Errorf("%s: only binary file type supported", operatorName)
|
||||||
}
|
}
|
||||||
if fileInfo.Descriptor.Length != len(fileInfo.Data) {
|
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
|
return existed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OperatorFactory is the factory that returns supermon operators
|
// XOperatorFactory is the factory that returns supermon operators
|
||||||
// given disk images.
|
// given disk images.
|
||||||
func OperatorFactory(diskbytes []byte) (disk.Operator, error) {
|
func XOperatorFactory(diskbytes []byte) (types.Operator, error) {
|
||||||
sm, err := LoadSectorMap(diskbytes)
|
sm, err := LoadSectorMap(diskbytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -794,7 +796,50 @@ func OperatorFactory(diskbytes []byte) (disk.Operator, error) {
|
|||||||
return nil, err
|
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)
|
st, err := sm.ReadSymbolTable(diskbytes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
package supermon
|
package supermon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/kr/pretty"
|
"github.com/kr/pretty"
|
||||||
"github.com/zellyn/diskii/disk"
|
"github.com/zellyn/diskii/disk"
|
||||||
|
"github.com/zellyn/diskii/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testDisk = "testdata/chacha20.dsk"
|
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
|
// loadSectorMap loads a sector map for the disk image contained in
|
||||||
// filename. It returns the sector map and a sector disk.
|
// filename. It returns the sector map and a sector disk.
|
||||||
func loadSectorMap(filename string) (SectorMap, disk.SectorDisk, error) {
|
func loadSectorMap(filename string) (SectorMap, []byte, error) {
|
||||||
sd, err := disk.LoadDSK(filename)
|
rawbytes, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
sm, err := LoadSectorMap(sd)
|
diskbytes, err := disk.Swizzle(rawbytes, disk.Dos33LogicalToPhysicalSectorMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
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
|
// 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
|
// TestGetFile tests the retrieval of a file's contents, using the
|
||||||
// Operator interface.
|
// Operator interface.
|
||||||
func TestGetFile(t *testing.T) {
|
func TestGetFile(t *testing.T) {
|
||||||
sd, err := disk.OpenDisk(testDisk)
|
op, err := getOperator(testDisk)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
op, err := disk.OperatorForDisk(sd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -213,20 +231,16 @@ func TestReadWriteSymbolTable(t *testing.T) {
|
|||||||
// TestPutFile tests the creation of a file, using the Operator
|
// TestPutFile tests the creation of a file, using the Operator
|
||||||
// interface.
|
// interface.
|
||||||
func TestPutFile(t *testing.T) {
|
func TestPutFile(t *testing.T) {
|
||||||
sd, err := disk.OpenDisk(testDisk)
|
op, err := getOperator(testDisk)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
op, err := disk.OperatorForDisk(sd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
contents := []byte(cities)
|
contents := []byte(cities)
|
||||||
fileInfo := disk.FileInfo{
|
fileInfo := types.FileInfo{
|
||||||
Descriptor: disk.Descriptor{
|
Descriptor: types.Descriptor{
|
||||||
Name: "FNEWFILE",
|
Name: "FNEWFILE",
|
||||||
Length: len(contents),
|
Length: len(contents),
|
||||||
Type: disk.FiletypeBinary,
|
Type: types.FiletypeBinary,
|
||||||
},
|
},
|
||||||
Data: contents,
|
Data: contents,
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@ package types
|
|||||||
|
|
||||||
// Globals holds flags and configuration that are shared globally.
|
// Globals holds flags and configuration that are shared globally.
|
||||||
type Globals struct {
|
type Globals struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
Order string //Logical-to-physical sector order
|
|
||||||
System string // DOS system used for image
|
|
||||||
|
|
||||||
DiskOperatorFactories []OperatorFactory
|
DiskOperatorFactories []OperatorFactory
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestBasicLoad(t *testing.T) {
|
func TestBasicLoad(t *testing.T) {
|
||||||
bb := data.MustAsset("data/disks/dos33master.woz")
|
wz, err := woz.Decode(bytes.NewReader(data.DOS33master_woz))
|
||||||
wz, err := woz.Decode(bytes.NewReader(bb))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user