mirror of https://github.com/zellyn/diskii.git
Merge pull request #2 from zellyn/refactor
Refactor to use kong instead of cobra
This commit is contained in:
commit
7d036244af
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"filewatcher.commands": [
|
||||
{
|
||||
"match": "\\.go",
|
||||
"cmd": "echo '${file} changed'",
|
||||
"event": "onFileChange",
|
||||
},
|
||||
]
|
||||
}
|
97
README.md
97
README.md
|
@ -28,11 +28,34 @@ diskii's major disadvantage is that it mostly doesn't exist yet.
|
|||
|
||||
It rhymes with “whiskey”.
|
||||
|
||||
Discussion/support is in
|
||||
[#apple2 on the retrocomputing Slack](https://retrocomputing.slack.com/messages/apple2/)
|
||||
(invites [here](https://retrocomputing.herokuapp.com)).
|
||||
Discussion/support is on the
|
||||
[apple2infinitum Slack](https://apple2infinitum.slack.com/)
|
||||
(invites [here](http://apple2.gs:3000/)).
|
||||
|
||||
### Goals
|
||||
# Examples
|
||||
|
||||
Get a listing of files on a DOS 3.3 disk image:
|
||||
```
|
||||
diskii ls dos33master.dsk
|
||||
```
|
||||
|
||||
… or a ProDOS disk image:
|
||||
```
|
||||
diskii ls ProDOS_2_4_2.po
|
||||
```
|
||||
|
||||
… or a Super-Mon disk image:
|
||||
```
|
||||
diskii ls Super-Mon-2.0.dsk
|
||||
```
|
||||
|
||||
Reorder the sectors in a disk image:
|
||||
```
|
||||
diskii reorder ProDOS_2_4_2.dsk ProDOS_2_4_2.po
|
||||
```
|
||||
|
||||
|
||||
# Goals
|
||||
|
||||
Eventually, it aims to be a comprehensive disk image manipulation
|
||||
tool, but for now only some parts work.
|
||||
|
@ -47,8 +70,8 @@ Current disk operations supported:
|
|||
| ---------------- | -------- | ------ | ------------------ |
|
||||
| basic structures | ✓ | ✓ | ✓ |
|
||||
| ls | ✓ | ✓ | ✓ |
|
||||
| dump | ✓ | ✗ | ✓ |
|
||||
| put | ✗ | ✗ | ✓ |
|
||||
| dump | ✗ | ✗ | ✗ |
|
||||
| put | ✗ | ✗ | ✗ |
|
||||
| dumptext | ✗ | ✗ | ✗ |
|
||||
| delete | ✗ | ✗ | ✗ |
|
||||
| rename | ✗ | ✗ | ✗ |
|
||||
|
@ -59,7 +82,7 @@ Current disk operations supported:
|
|||
| init | ✗ | ✗ | ✗ |
|
||||
| defrag | ✗ | ✗ | ✗ |
|
||||
|
||||
### Installing/updating
|
||||
# Installing/updating
|
||||
Assuming you have Go installed, run `go get -u github.com/zellyn/diskii`
|
||||
|
||||
You can also download automatically-built binaries from the
|
||||
|
@ -68,25 +91,24 @@ page](https://github.com/zellyn/diskii/releases/latest). If you
|
|||
need binaries for a different architecture, please send a pull
|
||||
request or open an issue.
|
||||
|
||||
### Short-term TODOs/roadmap/easy ways to contribute
|
||||
# Short-term TODOs/roadmap/easy ways to contribute
|
||||
|
||||
My rough TODO list (apart from anything marked (✗) in the disk
|
||||
operations matrix is listed below. Anything that an actual user needs
|
||||
will be likely to get priority.
|
||||
|
||||
- [ ] Make `put` accept load address for appropriate filetypes.
|
||||
- [ ] Implement `GetFile` for prodos
|
||||
- [x] Build per-platform binaries for Linux, MacOS, Windows.
|
||||
- [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
|
||||
- [ ] Add ProDOS support
|
||||
- [ ] Add ProDOS support for all commands
|
||||
- [x] Make `filetypes` command use a tabwriter to write as a table
|
||||
|
||||
### Related tools
|
||||
# Related tools
|
||||
|
||||
- http://a2ciderpress.com/ - the great grandaddy of them all. Windows only, unless you Wine
|
||||
- http://retrocomputingaustralia.com/rca-downloads/ Michael Mulhern's MacOS package of CiderPress
|
||||
|
@ -107,3 +129,48 @@ will be likely to get priority.
|
|||
- https://github.com/slotek/apple2-disk-util - ruby
|
||||
- https://github.com/slotek/dsk2nib - C
|
||||
- https://github.com/robmcmullen/atrcopy - dos3.3, python
|
||||
|
||||
# Notes
|
||||
|
||||
## Disk formats
|
||||
|
||||
- `.do`
|
||||
- `.po`
|
||||
- `.dsk` - could be DO or PO. When in doubt, assume DO.
|
||||
|
||||
| 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:
|
||||
|
||||
`00 0D 0B 09 07 05 03 01 0E 0C 0A 08 06 04 02 0F`
|
||||
|
||||
So if you write to "T0S1" with DOS RWTS, it ends up in physical sector 0D.
|
||||
|
||||
## Commandline examples for thinking about how it should work
|
||||
|
||||
diskii ls dos33.dsk
|
||||
diskii --order=do ls dos33.dsk
|
||||
diskii --order=do --system=nakedos ls nakedos.dsk
|
||||
|
|
|
@ -5,7 +5,7 @@ package applesoft
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zellyn/diskii/lib/basic"
|
||||
"github.com/zellyn/diskii/basic"
|
||||
)
|
||||
|
||||
// helloBinary is a simple basic program used for testing. Listing
|
|
@ -5,7 +5,7 @@ package integer
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zellyn/diskii/lib/basic"
|
||||
"github.com/zellyn/diskii/basic"
|
||||
)
|
||||
|
||||
// helloBinary is a simple basic program used for testing. Listing
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
export ACME="$HOME/gh/acme/ACME_Lib"
|
||||
ACME_BIN="$HOME/gh/acme/acme"
|
||||
|
||||
|
||||
$ACME_BIN -o writetest.o -r writetest.lst writetest.asm
|
||||
|
||||
# cp data/disks/dos33mst.dsk writetest.dsk
|
||||
# diskii put -f writetest.dsk writetest writetest.o
|
||||
|
||||
# Also run mame? (set MAMEDIR and MAMEBIN to your local variant)
|
||||
[[ -z "${MAMEDIR-}" ]] && MAMEDIR="/Users/zellyn/Library/Application Support/Ample"
|
||||
[[ -z "${MAMEBIN-}" ]] && MAMEBIN="/Applications/Ample.app/Contents/MacOS/mame64"
|
||||
|
||||
# Write audit.o into an OpenEmulator config?
|
||||
[[ -z "${TMPLS-}" ]] && TMPLS=~/gh/OpenEmulator-OSX/modules/libemulation/res/templates
|
||||
|
||||
DSK=$(realpath ./audit.dsk)
|
||||
|
||||
case "${1-none}" in
|
||||
"2ee")
|
||||
# mame64 apple2ee -skip_gameinfo -nosamples -window -resolution 1120x840 -flop1 /Users/zellyn/gh/a2audit/audit/audit.dsk
|
||||
(cd "$MAMEDIR"; "$MAMEBIN" apple2ee -window -flop1 "$DSK" -skip_gameinfo)
|
||||
;;
|
||||
"2e")
|
||||
(cd "$MAMEDIR"; "$MAMEBIN" apple2e -window -flop1 "$DSK" -skip_gameinfo)
|
||||
;;
|
||||
"2p")
|
||||
(cd "$MAMEDIR"; "$MAMEBIN" apple2p -window -flop1 "$DSK" -skip_gameinfo)
|
||||
;;
|
||||
"2")
|
||||
(cd "$MAMEDIR"; "$MAMEBIN" apple2 -window -flop1 "$DSK" -skip_gameinfo)
|
||||
;;
|
||||
"2ee-d")
|
||||
(cd "$MAMEDIR"; "$MAMEBIN" apple2ee -window -flop1 "$DSK" -skip_gameinfo -debug)
|
||||
;;
|
||||
"2e-d")
|
||||
(cd "$MAMEDIR"; "$MAMEBIN" apple2e -window -flop1 "$DSK" -skip_gameinfo -debug)
|
||||
;;
|
||||
"2p-d")
|
||||
(cd "$MAMEDIR"; "$MAMEBIN" apple2p -window -flop1 "$DSK" -skip_gameinfo -debug)
|
||||
;;
|
||||
"2-d")
|
||||
(cd "$MAMEDIR"; "$MAMEBIN" apple2 -window -flop1 "$DSK" -skip_gameinfo -debug)
|
||||
;;
|
||||
"oe")
|
||||
(head -c 24576 /dev/zero; cat audit.o; head -c 65536 /dev/zero) | head -c 65536 > $TMPLS/Apple\ II/Apple\ IIe-test.emulation/appleIIe.mainRam.bin
|
||||
sed -e 's|<property name="pc" value="0x...."/>|<property name="pc" value="0x6000"/>|' $TMPLS/Apple\ II/Apple\ IIe.xml > $TMPLS/Apple\ II/Apple\ IIe-test.emulation/info.xml
|
||||
;;
|
||||
"none")
|
||||
;;
|
||||
*)
|
||||
echo Options: 2ee, 2e, 2p, 2, 2ee-d, 2e-d, 2p-d, 2-d
|
||||
esac
|
||||
|
||||
true # Signal success (since we had a bunch of conditionals that can return false status).
|
|
@ -3,87 +3,41 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/basic"
|
||||
"github.com/zellyn/diskii/lib/basic/applesoft"
|
||||
"github.com/zellyn/diskii/lib/helpers"
|
||||
"github.com/zellyn/diskii/basic"
|
||||
"github.com/zellyn/diskii/basic/applesoft"
|
||||
"github.com/zellyn/diskii/helpers"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
// applesoftCmd represents the applesoft command
|
||||
var applesoftCmd = &cobra.Command{
|
||||
Use: "applesoft",
|
||||
Short: "work with applesoft programs",
|
||||
Long: `diskii applesoft contains the subcommands useful for working
|
||||
with Applesoft programs.`,
|
||||
type ApplesoftCmd struct {
|
||||
Decode DecodeCmd `kong:"cmd,help='Convert a binary Applesoft program to a text LISTing.'"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(applesoftCmd)
|
||||
type DecodeCmd struct {
|
||||
Filename string `kong:"arg,default='-',type='existingfile',help='Binary Applesoft file to read, or “-” for stdin.'"`
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// applesoftCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// applesoftCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
Location uint16 `kong:"type='anybaseuint16',default='0x801',help='Starting program location in memory.'"`
|
||||
Raw bool `kong:"short='r',help='Print raw control codes (no escaping)'"`
|
||||
}
|
||||
|
||||
// ----- applesoft decode command -------------------------------------------
|
||||
|
||||
var location uint16 // flag for starting location in memory
|
||||
var rawControlCodes bool // flag for whether to skip escaping control codes
|
||||
|
||||
// decodeCmd represents the decode command
|
||||
var decodeCmd = &cobra.Command{
|
||||
Use: "decode filename",
|
||||
Short: "convert a binary applesoft program to a LISTing",
|
||||
Long: `
|
||||
decode converts a binary Applesoft program to a text LISTing.
|
||||
|
||||
Examples:
|
||||
decode filename # read filename
|
||||
decode - # read stdin`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runDecode(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
},
|
||||
func (d DecodeCmd) Help() string {
|
||||
return `Examples:
|
||||
# Dump the contents of HELLO and then decode it.
|
||||
diskii dump dos33master.dsk HELLO | diskii applesoft decode -`
|
||||
}
|
||||
|
||||
func init() {
|
||||
applesoftCmd.AddCommand(decodeCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// decodeCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
decodeCmd.Flags().Uint16VarP(&location, "location", "l", 0x801, "Starting program location in memory")
|
||||
decodeCmd.Flags().BoolVarP(&rawControlCodes, "raw", "r", false, "Print raw control codes (no escaping)")
|
||||
}
|
||||
|
||||
// runDecode performs the actual decode logic.
|
||||
func runDecode(args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("decode expects one argument: the filename (or - for stdin)")
|
||||
}
|
||||
contents, err := helpers.FileContentsOrStdIn(args[0])
|
||||
func (d *DecodeCmd) Run(globals *types.Globals) error {
|
||||
contents, err := helpers.FileContentsOrStdIn(d.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listing, err := applesoft.Decode(contents, location)
|
||||
listing, err := applesoft.Decode(contents, d.Location)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rawControlCodes {
|
||||
if d.Raw {
|
||||
os.Stdout.WriteString(listing.String())
|
||||
} else {
|
||||
os.Stdout.WriteString(basic.ChevronControlCodes(listing.String()))
|
||||
|
|
|
@ -6,59 +6,39 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
var shortnames bool // flag for whether to print short filenames
|
||||
var debug bool
|
||||
type LsCmd struct {
|
||||
Order types.DiskOrder `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.'"`
|
||||
|
||||
// catalogCmd represents the cat command, used to catalog a disk or
|
||||
// directory.
|
||||
var catalogCmd = &cobra.Command{
|
||||
Use: "catalog",
|
||||
Aliases: []string{"cat", "ls"},
|
||||
Short: "print a list of files",
|
||||
Long: `Catalog a disk or subdirectory.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runCat(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
},
|
||||
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 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.
|
||||
func runCat(args []string) error {
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
return fmt.Errorf("cat expects a disk image filename, and an optional subdirectory")
|
||||
}
|
||||
op, err := disk.Open(args[0])
|
||||
func (l *LsCmd) Run(globals *types.Globals) error {
|
||||
op, order, err := disk.OpenFile(l.Image, l.Order, l.System, globals.DiskOperatorFactories, globals.Debug)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%w: %s", err, l.Image.Name())
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("Got disk of type %q with underlying sector/block order %q.\n", op.Name(), op.Order())
|
||||
if globals.Debug {
|
||||
fmt.Fprintf(os.Stderr, "Opened disk with order %q, system %q\n", order, op.Name())
|
||||
}
|
||||
subdir := ""
|
||||
if len(args) == 2 {
|
||||
|
||||
if l.Directory != "" {
|
||||
if !op.HasSubdirs() {
|
||||
return fmt.Errorf("Disks of type %q cannot have subdirectories", op.Name())
|
||||
}
|
||||
subdir = args[1]
|
||||
}
|
||||
fds, err := op.Catalog(subdir)
|
||||
fds, err := op.Catalog(l.Directory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fd := range fds {
|
||||
if !shortnames && fd.Fullname != "" {
|
||||
if !l.ShortNames && fd.Fullname != "" {
|
||||
fmt.Println(fd.Fullname)
|
||||
} else {
|
||||
fmt.Println(fd.Name)
|
||||
|
|
|
@ -4,61 +4,38 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
var missingok bool // flag for whether to consider deleting a nonexistent file okay
|
||||
type DeleteCmd struct {
|
||||
Order types.DiskOrder `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.'"`
|
||||
MissingOk bool `kong:"short='f',help='Overwrite existing file?'"`
|
||||
|
||||
// deleteCmd represents the delete command, used to delete a file.
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "delete a file",
|
||||
Long: `Delete a file.
|
||||
|
||||
delete disk-image.dsk HELLO
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runDelete(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
},
|
||||
DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to modify.'"`
|
||||
Filename string `kong:"arg,required,help='Filename to use on disk.'"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(deleteCmd)
|
||||
deleteCmd.Flags().BoolVarP(&missingok, "missingok", "f", false, "if true, don't consider deleting a nonexistent file an error")
|
||||
func (d DeleteCmd) Help() string {
|
||||
return `Examples:
|
||||
# Delete file GREMLINS on disk image games.dsk.
|
||||
diskii rm games.dsk GREMLINS`
|
||||
}
|
||||
|
||||
// runDelete performs the actual delete logic.
|
||||
func runDelete(args []string) error {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("delete expects a disk image filename, and a filename")
|
||||
}
|
||||
op, err := disk.Open(args[0])
|
||||
func (d *DeleteCmd) Run(globals *types.Globals) error {
|
||||
op, order, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deleted, err := op.Delete(args[1])
|
||||
|
||||
deleted, err := op.Delete(d.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !deleted && !missingok {
|
||||
return fmt.Errorf("file %q not found", args[1])
|
||||
if !deleted && !d.MissingOk {
|
||||
return fmt.Errorf("file %q not found (use -f to prevent this being an error)", d.Filename)
|
||||
}
|
||||
f, err := os.Create(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = op.Write(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return disk.WriteBack(d.DiskImage, op, order, true)
|
||||
}
|
||||
|
|
47
cmd/dump.go
47
cmd/dump.go
|
@ -3,48 +3,39 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
// dumpCmd represents the dump command, used to dump the raw contents
|
||||
// of a file.
|
||||
var dumpCmd = &cobra.Command{
|
||||
Use: "dump",
|
||||
Short: "dump the raw contents of a file",
|
||||
Long: `Dump the raw contents of a file.
|
||||
type DumpCmd struct {
|
||||
Order types.DiskOrder `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.'"`
|
||||
|
||||
dump disk-image.dsk HELLO
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runDump(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
},
|
||||
DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to modify.'"`
|
||||
Filename string `kong:"arg,required,help='Filename to use on disk.'"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(dumpCmd)
|
||||
func (d DumpCmd) Help() string {
|
||||
return `Examples:
|
||||
# Dump file GREMLINS on disk image games.dsk.
|
||||
diskii dump games.dsk GREMLINS`
|
||||
}
|
||||
|
||||
// runDump performs the actual dump logic.
|
||||
func runDump(args []string) error {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("dump expects a disk image filename, and a filename")
|
||||
}
|
||||
op, err := disk.Open(args[0])
|
||||
func (d *DumpCmd) Run(globals *types.Globals) error {
|
||||
op, _, err := disk.OpenFilename(d.DiskImage, d.Order, d.System, globals.DiskOperatorFactories, globals.Debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := op.GetFile(args[1])
|
||||
|
||||
file, err := op.GetFile(d.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stdout.Write(file.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(zellyn): allow writing to files
|
||||
os.Stdout.Write(file.Data)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,39 +5,22 @@ package cmd
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
var all bool // flag for whether to show all filetypes
|
||||
|
||||
// filetypesCmd represents the filetypes command, used to display
|
||||
// valid filetypes recognized by diskii.
|
||||
var filetypesCmd = &cobra.Command{
|
||||
Use: "filetypes",
|
||||
Short: "print a list of filetypes",
|
||||
Long: `Print a list of filetypes understood by diskii`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runFiletypes(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
},
|
||||
type FiletypesCmd struct {
|
||||
All bool `kong:"help='Display all types, including SOS types and reserved ranges.'"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(filetypesCmd)
|
||||
filetypesCmd.Flags().BoolVarP(&all, "all", "a", false, "display all types, including SOS types and reserved ranges")
|
||||
}
|
||||
|
||||
// runFiletypes performs the actual listing of filetypes.
|
||||
func runFiletypes(args []string) error {
|
||||
if len(args) != 0 {
|
||||
return fmt.Errorf("filetypes expects no arguments")
|
||||
}
|
||||
for _, typ := range disk.FiletypeNames(all) {
|
||||
fmt.Println(typ)
|
||||
func (f *FiletypesCmd) Run(globals *types.Globals) error {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintln(w, "Description\tName\tThree-letter Name\tOne-letter Name")
|
||||
fmt.Fprintln(w, "-----------\t----\t-----------------\t---------------")
|
||||
for _, typ := range types.FiletypeInfos(f.All) {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", typ.Desc, typ.Name, typ.ThreeLetter, typ.OneLetter)
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
|
129
cmd/nakedos.go
129
cmd/nakedos.go
|
@ -4,87 +4,76 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/lib/supermon"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/supermon"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
// nakedosCmd represents the nakedos command
|
||||
var nakedosCmd = &cobra.Command{
|
||||
Use: "nakedos",
|
||||
Short: "work with NakedOS disks",
|
||||
Long: `diskii nakedos contains the subcommands useful for working
|
||||
with NakedOS (and Super-Mon) disks`,
|
||||
Aliases: []string{"supermon"},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(nakedosCmd)
|
||||
}
|
||||
|
||||
// ----- mkhello command ----------------------------------------------------
|
||||
|
||||
var address uint16 // flag for address to load at
|
||||
var start uint16 // flag for address to start execution at
|
||||
const helloName = "FHELLO" // filename to use (if Super-Mon)
|
||||
|
||||
// mkhelloCmd represents the mkhello command
|
||||
var mkhelloCmd = &cobra.Command{
|
||||
Use: "mkhello <disk-image> filename",
|
||||
Short: "create an FHELLO program that loads and runs another file",
|
||||
Long: `
|
||||
mkhello creates file DF01:FHELLO that loads and runs another program at a specific address.
|
||||
type NakedOSCmd struct {
|
||||
Mkhello MkHelloCmd `kong:"cmd,help='Create an FHELLO program that loads and runs another file.'"`
|
||||
}
|
||||
|
||||
// Help shows extended help on NakedOS/Super-Mon.
|
||||
func (n NakedOSCmd) Help() string {
|
||||
return `NakedOS and Super-Mon were created by the amazing Martin Haye. For more information see:
|
||||
Source/docs: https://bitbucket.org/martin.haye/super-mon/
|
||||
Presentation: https://www.kansasfest.org/2012/08/2010-haye-nakedos/`
|
||||
}
|
||||
|
||||
type MkHelloCmd struct {
|
||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
||||
|
||||
DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to modify.'"`
|
||||
Filename string `kong:"arg,required,help='Name of NakedOS file to load.'"`
|
||||
|
||||
Address uint16 `kong:"type='anybaseuint16',default='0x6000',help='Address to load the code at.'"`
|
||||
Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"`
|
||||
}
|
||||
|
||||
func (m MkHelloCmd) Help() string {
|
||||
return `This command creates a very short DF01:FHELLO program that simply loads another program of your choice.
|
||||
|
||||
Examples:
|
||||
mkhello test.dsk FDEMO # load and run FDEMO at the default address, then jump to the start of the loaded code.
|
||||
mkhello test.dsk --address 0x2000 --start 0x2100 DF06 # load and run file DF06 at address 0x2000, and jump to 0x2100.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runMkhello(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
},
|
||||
# Load and run FDEMO at the default address, then jump to the start of the loaded code.
|
||||
mkhello test.dsk FDEMO
|
||||
|
||||
# Load and run file DF06 at address 0x2000, and jump to 0x2100.
|
||||
mkhello test.dsk --address 0x2000 --start 0x2100 DF06`
|
||||
}
|
||||
|
||||
func init() {
|
||||
nakedosCmd.AddCommand(mkhelloCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
mkhelloCmd.Flags().Uint16VarP(&address, "address", "a", 0x6000, "memory location to load code at")
|
||||
mkhelloCmd.Flags().Uint16VarP(&start, "start", "s", 0x6000, "memory location to jump to")
|
||||
}
|
||||
|
||||
// runMkhello performs the actual mkhello logic.
|
||||
func runMkhello(args []string) error {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("usage: diskii mkhello <disk image> <file-to-load>")
|
||||
func (m *MkHelloCmd) Run(globals *types.Globals) error {
|
||||
if m.Start == 0xFFFF {
|
||||
m.Start = m.Address
|
||||
}
|
||||
if address%256 != 0 {
|
||||
return fmt.Errorf("address %d (%04X) not on a page boundary", address, address)
|
||||
|
||||
if m.Address%256 != 0 {
|
||||
return fmt.Errorf("address %d (%04X) not on a page boundary", m.Address, m.Address)
|
||||
}
|
||||
if start < address {
|
||||
return fmt.Errorf("start address %d (%04X) < load address %d (%04X)", start, start, address, address)
|
||||
if m.Start < m.Address {
|
||||
return fmt.Errorf("start address %d (%04X) < load address %d (%04X)", m.Start, m.Start, m.Address, m.Address)
|
||||
}
|
||||
op, err := disk.Open(args[0])
|
||||
|
||||
op, order, err := disk.OpenFilename(m.DiskImage, m.Order, "auto", globals.DiskOperatorFactories, globals.Debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if op.Name() != "nakedos" {
|
||||
return fmt.Errorf("mkhello only works on disks of type %q; got %q", "nakedos", op.Name())
|
||||
}
|
||||
nakOp, ok := op.(supermon.Operator)
|
||||
if !ok {
|
||||
return fmt.Errorf("internal error: cannot cast to expected supermon.Operator type")
|
||||
return fmt.Errorf("internal error: cannot cast to expected supermon.Operator type (got %T)", op)
|
||||
}
|
||||
addr, symbolAddr, _, err := nakOp.ST.FilesForCompoundName(args[1])
|
||||
addr, symbolAddr, _, err := nakOp.ST.FilesForCompoundName(m.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if addr == 0 && symbolAddr == 0 {
|
||||
return fmt.Errorf("cannot parse %q as valid filename", args[1])
|
||||
return fmt.Errorf("cannot parse %q as valid filename", m.Filename)
|
||||
}
|
||||
toLoad := addr
|
||||
if addr == 0 {
|
||||
|
@ -94,32 +83,24 @@ func runMkhello(args []string) error {
|
|||
0x20, 0x40, 0x03, // JSR NAKEDOS
|
||||
0x6D, 0x01, 0xDC, // ADC NKRDFILE
|
||||
0x2C, toLoad, 0xDF, // BIT ${file number to load}
|
||||
0x2C, 0x00, byte(address >> 8), // BIT ${target page}
|
||||
0xD8, // CLD
|
||||
0x4C, byte(start), byte(start >> 8), // JMP ${target page}
|
||||
0x2C, 0x00, byte(m.Address >> 8), // BIT ${target page}
|
||||
0xD8, // CLD
|
||||
0x4C, byte(m.Start), byte(m.Start >> 8), // JMP ${target page}
|
||||
}
|
||||
fileInfo := disk.FileInfo{
|
||||
Descriptor: disk.Descriptor{
|
||||
fileInfo := types.FileInfo{
|
||||
Descriptor: types.Descriptor{
|
||||
Name: fmt.Sprintf("DF01:%s", helloName),
|
||||
Length: len(contents),
|
||||
Type: disk.FiletypeBinary,
|
||||
Type: types.FiletypeBinary,
|
||||
},
|
||||
Data: contents,
|
||||
}
|
||||
_ = fileInfo
|
||||
|
||||
_, err = op.PutFile(fileInfo, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Create(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = op.Write(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return disk.WriteBack(m.DiskImage, op, order, true)
|
||||
}
|
||||
|
|
87
cmd/put.go
87
cmd/put.go
|
@ -4,80 +4,61 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/lib/helpers"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/helpers"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
var filetypeName string // flag for file type
|
||||
var overwrite bool // flag for whether to overwrite
|
||||
type PutCmd struct {
|
||||
Order types.DiskOrder `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.'"`
|
||||
FiletypeName string `kong:"default='B',help='Type of file (“diskii filetypes” to list).'"`
|
||||
Overwrite bool `kong:"short='f',help='Overwrite existing file?'"`
|
||||
|
||||
// putCmd represents the put command, used to put the raw contents
|
||||
// of a file.
|
||||
var putCmd = &cobra.Command{
|
||||
Use: "put",
|
||||
Short: "put the raw contents of a file",
|
||||
Long: `Put the raw contents of a file.
|
||||
DiskImage string `kong:"arg,required,type='existingfile',help='Disk image to modify.'"`
|
||||
TargetFilename string `kong:"arg,required,help='Filename to use on disk.'"`
|
||||
SourceFilename string `kong:"arg,required,type='existingfile',help='Name of file containing data to put.'"`
|
||||
}
|
||||
|
||||
put disk-image.dsk HELLO <name of file with contents>
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runPut(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
func (p PutCmd) Help() string {
|
||||
return `Examples:
|
||||
# Put file gremlins.o onto disk image games.dsk, using the filename GREMLINS.
|
||||
diskii put games.dsk GREMLINS gremlins.o`
|
||||
}
|
||||
|
||||
func (p *PutCmd) Run(globals *types.Globals) error {
|
||||
if p.DiskImage == "-" {
|
||||
if p.SourceFilename == "-" {
|
||||
return fmt.Errorf("cannot get both disk image and file contents from stdin")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(putCmd)
|
||||
putCmd.Flags().StringVarP(&filetypeName, "type", "t", "B", "Type of file (`diskii filetypes` to list)")
|
||||
putCmd.Flags().BoolVarP(&overwrite, "overwrite", "f", false, "whether to overwrite existing files")
|
||||
}
|
||||
|
||||
// runPut performs the actual put logic.
|
||||
func runPut(args []string) error {
|
||||
if len(args) != 3 {
|
||||
return fmt.Errorf("usage: put <disk image> <target filename> <source filename>")
|
||||
}
|
||||
op, err := disk.Open(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contents, err := helpers.FileContentsOrStdIn(args[2])
|
||||
op, order, err := disk.OpenFilename(p.DiskImage, p.Order, p.System, globals.DiskOperatorFactories, globals.Debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filetype, err := disk.FiletypeForName(filetypeName)
|
||||
contents, err := helpers.FileContentsOrStdIn(p.SourceFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileInfo := disk.FileInfo{
|
||||
Descriptor: disk.Descriptor{
|
||||
Name: args[1],
|
||||
filetype, err := types.FiletypeForName(p.FiletypeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileInfo := types.FileInfo{
|
||||
Descriptor: types.Descriptor{
|
||||
Name: p.TargetFilename,
|
||||
Length: len(contents),
|
||||
Type: filetype,
|
||||
},
|
||||
Data: contents,
|
||||
}
|
||||
_, err = op.PutFile(fileInfo, overwrite)
|
||||
_, err = op.PutFile(fileInfo, p.Overwrite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Create(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = op.Write(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return disk.WriteBack(p.DiskImage, op, order, true)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/helpers"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
type ReorderCmd struct {
|
||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
||||
NewOrder types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='New Logical-to-physical sector order.'"`
|
||||
Overwrite bool `kong:"short='f',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
|
||||
}
|
||||
|
||||
overwrite := r.Overwrite
|
||||
filename := r.NewDiskImage
|
||||
if filename == "" {
|
||||
filename = r.DiskImage
|
||||
overwrite = true
|
||||
}
|
||||
return helpers.WriteOutput(filename, tobytes, overwrite)
|
||||
}
|
||||
|
||||
// getOrders returns the input order, and the output order.
|
||||
func getOrders(inFilename string, inOrder types.DiskOrder, outFilename string, outOrder types.DiskOrder) (types.DiskOrder, types.DiskOrder, 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 := disk.OrderFromFilename(inFilename, types.DiskOrderUnknown), disk.OrderFromFilename(outFilename, types.DiskOrderUnknown)
|
||||
if inGuess == outGuess {
|
||||
if inGuess == types.DiskOrderUnknown {
|
||||
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 == types.DiskOrderUnknown {
|
||||
return oppositeOrder(outGuess), outGuess, nil
|
||||
}
|
||||
if outGuess == types.DiskOrderUnknown {
|
||||
return inGuess, oppositeOrder(inGuess), nil
|
||||
}
|
||||
return inGuess, outGuess, nil
|
||||
}
|
||||
|
||||
// oppositeOrder returns the opposite order from the input.
|
||||
func oppositeOrder(order types.DiskOrder) types.DiskOrder {
|
||||
if order == types.DiskOrderDO {
|
||||
return types.DiskOrderPO
|
||||
}
|
||||
return types.DiskOrderDO
|
||||
}
|
61
cmd/root.go
61
cmd/root.go
|
@ -1,61 +0,0 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "diskii",
|
||||
Short: "Operate on Apple II disk images and their contents",
|
||||
Long: `diskii is a commandline tool for working with Apple II disk
|
||||
images.
|
||||
|
||||
Eventually, it aims to be a comprehensive disk image manipulation tool.`,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports Persistent Flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.diskii.yaml)")
|
||||
// Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
// RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" { // enable ability to specify config file via flag
|
||||
viper.SetConfigFile(cfgFile)
|
||||
}
|
||||
|
||||
viper.SetConfigName(".diskii") // name of config file (without extension)
|
||||
viper.AddConfigPath("$HOME") // adding home directory as first search path
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
109
cmd/sd.go
109
cmd/sd.go
|
@ -4,68 +4,62 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/lib/helpers"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/helpers"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
var sdAddress uint16 // flag for address to load at
|
||||
var sdStart uint16 // flag for address to start execution at
|
||||
type SDCmd struct {
|
||||
Order types.DiskOrder `kong:"default='auto',enum='auto,do,po',help='Logical-to-physical sector order.'"`
|
||||
|
||||
// mksdCmd represents the mksd command
|
||||
var mksdCmd = &cobra.Command{
|
||||
Use: "mksd",
|
||||
Short: "create a Standard-Delivery disk image",
|
||||
Long: `diskii mksd creates a "Standard Delivery" disk image containing a binary.
|
||||
DiskImage string `kong:"arg,required,type='path',help='Disk image to write.'"`
|
||||
Binary *os.File `kong:"arg,required,help='Binary file to write to the disk.'"`
|
||||
|
||||
Address uint16 `kong:"type='anybaseuint16',default='0x6000',help='Address to load the code at.'"`
|
||||
Start uint16 `kong:"type='anybaseuint16',default='0xFFFF',help='Address to jump to. Defaults to 0xFFFF, which means “same as address flag”'"`
|
||||
}
|
||||
|
||||
func (s SDCmd) Help() string {
|
||||
return `
|
||||
See https://github.com/peterferrie/standard-delivery for details.
|
||||
|
||||
Examples:
|
||||
mksd test.dsk foo.o # load and run foo.o at the default address, then jump to the start of the loaded code.
|
||||
mksd test.dsk foo.o --address 0x2000 --start 0x2100 # load foo.o at address 0x2000, then jump to 0x2100.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runMkSd(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
},
|
||||
# Load and run foo.o at the default address, then jump to the start of the loaded code.
|
||||
diskii mksd test.dsk foo.o
|
||||
|
||||
# Load foo.o at address 0x2000, then jump to 0x2100.
|
||||
diskii mksd test.dsk foo.o --address 0x2000 --start 0x2100`
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(mksdCmd)
|
||||
mksdCmd.Flags().Uint16VarP(&sdAddress, "address", "a", 0x6000, "memory location to load code at")
|
||||
mksdCmd.Flags().Uint16VarP(&sdStart, "start", "s", 0x6000, "memory location to jump to")
|
||||
}
|
||||
|
||||
// ----- mksd command -------------------------------------------------------
|
||||
|
||||
// runMkSd performs the actual mksd logic.
|
||||
func runMkSd(args []string) error {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("usage: diskii mksd <disk image> <file-to-load>")
|
||||
func (s *SDCmd) Run(globals *types.Globals) error {
|
||||
if s.Start == 0xFFFF {
|
||||
s.Start = s.Address
|
||||
}
|
||||
contents, err := helpers.FileContentsOrStdIn(args[1])
|
||||
|
||||
contents, err := io.ReadAll(s.Binary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sdAddress%256 != 0 {
|
||||
return fmt.Errorf("address %d (%04X) not on a page boundary", sdAddress, sdAddress)
|
||||
if s.Address%256 != 0 {
|
||||
return fmt.Errorf("address %d (%04X) not on a page boundary", s.Address, s.Address)
|
||||
}
|
||||
if sdStart < sdAddress {
|
||||
return fmt.Errorf("start address %d (%04X) < load address %d (%04X)", sdStart, sdStart, sdAddress, sdAddress)
|
||||
if s.Start < s.Address {
|
||||
return fmt.Errorf("start address %d (%04X) < load address %d (%04X)", s.Start, s.Start, s.Address, s.Address)
|
||||
}
|
||||
|
||||
if int(sdStart) >= int(sdAddress)+len(contents) {
|
||||
end := int(sdAddress) + len(contents)
|
||||
if int(s.Start) >= int(s.Address)+len(contents) {
|
||||
end := int(s.Address) + len(contents)
|
||||
return fmt.Errorf("start address %d (%04X) is beyond load address %d (%04X) + file length = %d (%04X)",
|
||||
sdStart, sdStart, sdAddress, sdAddress, end, end)
|
||||
s.Start, s.Start, s.Address, s.Address, end, end)
|
||||
}
|
||||
|
||||
if int(sdStart)+len(contents) > 0xC000 {
|
||||
end := int(sdStart) + len(contents)
|
||||
if int(s.Start)+len(contents) > 0xC000 {
|
||||
end := int(s.Start) + len(contents)
|
||||
return fmt.Errorf("start address %d (%04X) + file length %d (%04X) = %d (%04X), but we can't load past page 0xBF00",
|
||||
sdStart, sdStart, len(contents), len(contents), end, end)
|
||||
s.Start, s.Start, len(contents), len(contents), end, end)
|
||||
}
|
||||
|
||||
sectors := (len(contents) + 255) / 256
|
||||
|
@ -76,35 +70,35 @@ func runMkSd(args []string) error {
|
|||
0xc8, 0xa5, 0x27, 0xf0, 0xdf, 0x8a, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x48, 0xa9, 0x5b,
|
||||
0x48, 0x60, 0xe6, 0x41, 0x06, 0x40, 0x20, 0x37, 0x08, 0x18, 0x20, 0x3c, 0x08, 0xe6, 0x40,
|
||||
0xa5, 0x40, 0x29, 0x03, 0x2a, 0x05, 0x2b, 0xa8, 0xb9, 0x80, 0xc0, 0xa9, 0x30, 0x4c, 0xa8,
|
||||
0xfc, 0x4c, byte(sdStart), byte(sdStart >> 8),
|
||||
0xfc, 0x4c, byte(s.Start), byte(s.Start >> 8),
|
||||
}
|
||||
|
||||
if len(loader)+sectors+1 > 256 {
|
||||
return fmt.Errorf("file %q is %d bytes long, max is %d", args[1], len(contents), (255-len(loader))*256)
|
||||
return fmt.Errorf("file %q is %d bytes long, max is %d", s.Binary.Name(), len(contents), (255-len(loader))*256)
|
||||
}
|
||||
|
||||
for len(contents)%256 != 0 {
|
||||
contents = append(contents, 0)
|
||||
}
|
||||
|
||||
sd := disk.Empty()
|
||||
diskbytes := make([]byte, disk.FloppyDiskBytes)
|
||||
|
||||
var track, sector byte
|
||||
for i := 0; i < len(contents); i += 256 {
|
||||
sector += 2
|
||||
if sector >= sd.Sectors() {
|
||||
sector = (sd.Sectors() + 1) - sector
|
||||
if sector >= disk.FloppySectors {
|
||||
sector = (disk.FloppySectors + 1) - sector
|
||||
if sector == 0 {
|
||||
track++
|
||||
if track >= sd.Tracks() {
|
||||
if track >= disk.FloppyTracks {
|
||||
return fmt.Errorf("ran out of tracks")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
address := int(sdAddress) + i
|
||||
address := int(s.Address) + i
|
||||
loader = append(loader, byte(address>>8))
|
||||
if err := sd.WritePhysicalSector(track, sector, contents[i:i+256]); err != nil {
|
||||
if err := disk.WriteSector(diskbytes, track, sector, contents[i:i+256]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -114,20 +108,17 @@ func runMkSd(args []string) error {
|
|||
loader = append(loader, 0)
|
||||
}
|
||||
|
||||
if err := sd.WritePhysicalSector(0, 0, loader); err != nil {
|
||||
if err := disk.WriteSector(diskbytes, 0, 0, loader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(args[0])
|
||||
order := s.Order
|
||||
if order == types.DiskOrderAuto {
|
||||
order = disk.OrderFromFilename(s.DiskImage, types.DiskOrderDO)
|
||||
}
|
||||
rawBytes, err := disk.Swizzle(diskbytes, disk.PhysicalToLogicalByName[order])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sd.Write(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return helpers.WriteOutput(s.DiskImage, rawBytes, true)
|
||||
}
|
||||
|
|
321
data/data.go
321
data/data.go
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -10,14 +10,15 @@ import (
|
|||
"io/ioutil"
|
||||
)
|
||||
|
||||
// A ProDOS block.
|
||||
type Block [512]byte
|
||||
|
||||
// Dev represents a .po disk image.
|
||||
type Dev struct {
|
||||
data []byte // The actual data in the file
|
||||
blocks uint16 // Number of blocks
|
||||
}
|
||||
|
||||
var _ BlockDevice = (*Dev)(nil)
|
||||
|
||||
// LoadDev loads a .po image from a file.
|
||||
func LoadDev(filename string) (Dev, error) {
|
||||
bb, err := ioutil.ReadFile(filename)
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// Package disk contains routines for reading and writing various disk
|
||||
// file formats.
|
||||
package disk
|
||||
|
||||
import "github.com/zellyn/diskii/types"
|
||||
|
||||
// Various DOS33 disk characteristics.
|
||||
const (
|
||||
FloppyTracks = 35
|
||||
FloppySectors = 16 // Sectors per track
|
||||
// FloppyDiskBytes is the number of bytes on a DOS 3.3 disk.
|
||||
FloppyDiskBytes = 143360 // 35 tracks * 16 sectors * 256 bytes
|
||||
FloppyTrackBytes = 256 * FloppySectors // Bytes per track
|
||||
FloppyDiskBytes13Sector = 35 * 13 * 256
|
||||
)
|
||||
|
||||
// Dos33LogicalToPhysicalSectorMap maps logical sector numbers to physical ones.
|
||||
// See [UtA2 9-42 - Read Routines].
|
||||
var Dos33LogicalToPhysicalSectorMap = []int{
|
||||
0x00, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x01,
|
||||
0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x0F,
|
||||
}
|
||||
|
||||
// Dos33PhysicalToLogicalSectorMap maps physical sector numbers to logical ones.
|
||||
// See [UtA2 9-42 - Read Routines].
|
||||
var Dos33PhysicalToLogicalSectorMap = []int{
|
||||
0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04,
|
||||
0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F,
|
||||
}
|
||||
|
||||
// ProDOSLogicalToPhysicalSectorMap maps logical sector numbers to pysical ones.
|
||||
// See [UtA2e 9-43 - Sectors vs. Blocks].
|
||||
var ProDOSLogicalToPhysicalSectorMap = []int{
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
|
||||
0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
}
|
||||
|
||||
// ProDosPhysicalToLogicalSectorMap maps physical sector numbers to logical ones.
|
||||
// See [UtA2e 9-43 - Sectors vs. Blocks].
|
||||
var ProDosPhysicalToLogicalSectorMap = []int{
|
||||
0x00, 0x08, 0x01, 0x09, 0x02, 0x0A, 0x03, 0x0B,
|
||||
0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F,
|
||||
}
|
||||
|
||||
// LogicalToPhysicalByName maps from "do" and "po" to the corresponding
|
||||
// logical-to-physical ordering.
|
||||
var LogicalToPhysicalByName map[types.DiskOrder][]int = map[types.DiskOrder][]int{
|
||||
types.DiskOrderDO: Dos33LogicalToPhysicalSectorMap,
|
||||
types.DiskOrderPO: ProDOSLogicalToPhysicalSectorMap,
|
||||
types.DiskOrderRaw: {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
}
|
||||
|
||||
// PhysicalToLogicalByName maps from "do" and "po" to the corresponding
|
||||
// physical-to-logical ordering.
|
||||
var PhysicalToLogicalByName map[types.DiskOrder][]int = map[types.DiskOrder][]int{
|
||||
types.DiskOrderDO: Dos33PhysicalToLogicalSectorMap,
|
||||
types.DiskOrderPO: ProDosPhysicalToLogicalSectorMap,
|
||||
types.DiskOrderRaw: {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
}
|
||||
|
||||
// TrackSector is a pair of track/sector bytes.
|
||||
type TrackSector struct {
|
||||
Track byte
|
||||
Sector byte
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// marshal.go contains helpers for marshaling sector structs to/from
|
||||
// disk and block structs to/from devices.
|
||||
|
||||
package disk
|
||||
|
||||
import "fmt"
|
||||
|
||||
// BlockDevice is the interface used to read and write devices by
|
||||
// logical block number.
|
||||
|
||||
// SectorSource is the interface for types that can marshal to sectors.
|
||||
type SectorSource interface {
|
||||
// ToSector marshals the sector struct to exactly 256 bytes.
|
||||
ToSector() ([]byte, error)
|
||||
// GetTrack returns the track that a sector struct was loaded from.
|
||||
GetTrack() byte
|
||||
// GetSector returns the sector that a sector struct was loaded from.
|
||||
GetSector() byte
|
||||
}
|
||||
|
||||
// SectorSink is the interface for types that can unmarshal from sectors.
|
||||
type SectorSink interface {
|
||||
// FromSector unmarshals the sector struct from bytes. Input is
|
||||
// expected to be exactly 256 bytes.
|
||||
FromSector(data []byte) error
|
||||
// SetTrack sets the track that a sector struct was loaded from.
|
||||
SetTrack(track byte)
|
||||
// SetSector sets the sector that a sector struct was loaded from.
|
||||
SetSector(sector byte)
|
||||
}
|
||||
|
||||
// UnmarshalLogicalSector reads a sector from a disk image, and unmarshals it
|
||||
// into a SectorSink, setting its track and sector.
|
||||
func UnmarshalLogicalSector(diskbytes []byte, ss SectorSink, track, sector byte) error {
|
||||
bytes, err := ReadSector(diskbytes, track, sector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ss.FromSector(bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
ss.SetTrack(track)
|
||||
ss.SetSector(sector)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadSector just reads 256 bytes from the given track and sector.
|
||||
func ReadSector(diskbytes []byte, track, sector byte) ([]byte, error) {
|
||||
start := int(track)*FloppyTrackBytes + int(sector)*256
|
||||
end := start + 256
|
||||
if len(diskbytes) < end {
|
||||
return nil, fmt.Errorf("cannot read track %d/sector %d (bytes %d-%d) from disk of length %d", track, sector, start, end, len(diskbytes))
|
||||
}
|
||||
bytes := make([]byte, 256)
|
||||
copy(bytes, diskbytes[start:end])
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// MarshalLogicalSector marshals a SectorSource to its track/sector on a disk
|
||||
// image.
|
||||
func MarshalLogicalSector(diskbytes []byte, ss SectorSource) error {
|
||||
track := ss.GetTrack()
|
||||
sector := ss.GetSector()
|
||||
bytes, err := ss.ToSector()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteSector(diskbytes, track, sector, bytes)
|
||||
}
|
||||
|
||||
// WriteSector writes 256 bytes to the given track and sector.
|
||||
func WriteSector(diskbytes []byte, track, sector byte, data []byte) error {
|
||||
if len(data) != 256 {
|
||||
return fmt.Errorf("call to writeSector with len(data)==%d; want 256", len(data))
|
||||
}
|
||||
start := int(track)*FloppyTrackBytes + int(sector)*256
|
||||
end := start + 256
|
||||
if len(diskbytes) < end {
|
||||
return fmt.Errorf("cannot write track %d/sector %d (bytes %d-%d) to disk of length %d", track, sector, start, end, len(diskbytes))
|
||||
}
|
||||
copy(diskbytes[start:end], data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockSource is the interface for types that can marshal to blocks.
|
||||
type BlockSource interface {
|
||||
// ToBlock marshals the block struct to exactly 512 bytes.
|
||||
ToBlock() (Block, error)
|
||||
// GetBlock returns the index that a block struct was loaded from.
|
||||
GetBlock() uint16
|
||||
}
|
||||
|
||||
// BlockSink is the interface for types that can unmarshal from blocks.
|
||||
type BlockSink interface {
|
||||
// FromBlock unmarshals the block struct from a Block. Input is
|
||||
// expected to be exactly 512 bytes.
|
||||
FromBlock(block Block) error
|
||||
// SetBlock sets the index that a block struct was loaded from.
|
||||
SetBlock(index uint16)
|
||||
}
|
||||
|
||||
// UnmarshalBlock reads a block from a block device, and unmarshals it into a
|
||||
// BlockSink, setting its index.
|
||||
func UnmarshalBlock(diskbytes []byte, bs BlockSink, index uint16) error {
|
||||
start := int(index) * 512
|
||||
end := start + 512
|
||||
if len(diskbytes) < end {
|
||||
return fmt.Errorf("device too small to read block %d", index)
|
||||
}
|
||||
var block Block
|
||||
copy(block[:], diskbytes[start:end])
|
||||
if err := bs.FromBlock(block); err != nil {
|
||||
return err
|
||||
}
|
||||
bs.SetBlock(index)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBlock marshals a BlockSource to its block on a block device.
|
||||
func MarshalBlock(diskbytes []byte, bs BlockSource) error {
|
||||
index := bs.GetBlock()
|
||||
block, err := bs.ToBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start := int(index) * 512
|
||||
end := start + 512
|
||||
if len(diskbytes) < end {
|
||||
return fmt.Errorf("device too small to write block %d", index)
|
||||
}
|
||||
copy(diskbytes[start:end], block[:])
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/zellyn/diskii/helpers"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
// OpenFilename attempts to open a disk or device image, using the provided ordering and system type.
|
||||
func OpenFilename(filename string, order types.DiskOrder, system string, operatorFactories []types.OperatorFactory, debug bool) (types.Operator, types.DiskOrder, error) {
|
||||
if filename == "-" {
|
||||
return OpenFile(os.Stdin, order, system, operatorFactories, debug)
|
||||
}
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return OpenFile(file, order, system, operatorFactories, debug)
|
||||
}
|
||||
|
||||
// OpenImage attempts to open a disk or device image, using the provided ordering and system type.
|
||||
// OpenImage will close the file.
|
||||
func OpenFile(file *os.File, order types.DiskOrder, system string, operatorFactories []types.OperatorFactory, debug bool) (types.Operator, types.DiskOrder, error) {
|
||||
bb, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return OpenImage(bb, file.Name(), order, system, operatorFactories, debug)
|
||||
}
|
||||
|
||||
// OpenImage attempts to open a disk or device image, using the provided ordering and system type.
|
||||
func OpenImage(filebytes []byte, filename string, order types.DiskOrder, system string, operatorFactories []types.OperatorFactory, debug bool) (types.Operator, types.DiskOrder, error) {
|
||||
ext := strings.ToLower(path.Ext(filename))
|
||||
size := len(filebytes)
|
||||
if size == FloppyDiskBytes {
|
||||
return openDoOrPo(filebytes, order, system, ext, operatorFactories, debug)
|
||||
}
|
||||
if size == FloppyDiskBytes13Sector {
|
||||
return nil, "", fmt.Errorf("cannot open 13-sector disk images (yet)")
|
||||
}
|
||||
|
||||
if ext == ".hdv" {
|
||||
return openHDV(filebytes, order, system, operatorFactories, debug)
|
||||
}
|
||||
return nil, "", fmt.Errorf("can only open disk-sized images and .hdv files")
|
||||
}
|
||||
|
||||
func openHDV(rawbytes []byte, order types.DiskOrder, system string, operatorFactories []types.OperatorFactory, debug bool) (types.Operator, types.DiskOrder, error) {
|
||||
size := len(rawbytes)
|
||||
if size%512 > 0 {
|
||||
return nil, "", fmt.Errorf("can only open .hdv files that are a multiple of 512 bytes: %d %% 512 == %d", size, size%512)
|
||||
}
|
||||
if size/512 > 65536 {
|
||||
return nil, "", fmt.Errorf("can only open .hdv up to size 32MiB (%d); got %d", 65536*512, size)
|
||||
}
|
||||
if order != "auto" && order != types.DiskOrderPO {
|
||||
return nil, "", fmt.Errorf("cannot open .hdv file in %q order", order)
|
||||
}
|
||||
if system != "auto" && system != "prodos" {
|
||||
return nil, "", fmt.Errorf("cannot open .hdv file with %q system", system)
|
||||
}
|
||||
for _, factory := range operatorFactories {
|
||||
if factory.Name() == "prodos" {
|
||||
op, err := factory.Operator(rawbytes, debug)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return op, types.DiskOrderPO, nil
|
||||
}
|
||||
}
|
||||
return nil, "", fmt.Errorf("unable to find prodos module to open .hdv file") // Should not happen.
|
||||
}
|
||||
|
||||
func openDoOrPo(rawbytes []byte, order types.DiskOrder, system string, ext string, operatorFactories []types.OperatorFactory, debug bool) (types.Operator, types.DiskOrder, error) {
|
||||
var factories []types.OperatorFactory
|
||||
for _, factory := range operatorFactories {
|
||||
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", system)
|
||||
}
|
||||
orders := []types.DiskOrder{order}
|
||||
switch order {
|
||||
case types.DiskOrderDO, types.DiskOrderPO:
|
||||
// nothing more
|
||||
case types.DiskOrderAuto:
|
||||
switch ext {
|
||||
case ".po":
|
||||
orders = []types.DiskOrder{types.DiskOrderPO}
|
||||
case ".do":
|
||||
orders = []types.DiskOrder{types.DiskOrderDO}
|
||||
case ".dsk", "":
|
||||
orders = []types.DiskOrder{types.DiskOrderDO, types.DiskOrderPO}
|
||||
default:
|
||||
return nil, "", fmt.Errorf("unknown disk image extension: %q", ext)
|
||||
}
|
||||
default:
|
||||
return nil, "", fmt.Errorf("disk order %q invalid for %d-byte disk images", order, FloppyDiskBytes)
|
||||
}
|
||||
|
||||
for _, order := range orders {
|
||||
swizzled, err := Swizzle(rawbytes, LogicalToPhysicalByName[order])
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
for _, factory := range factories {
|
||||
diskbytes, err := Swizzle(swizzled, PhysicalToLogicalByName[factory.DiskOrder()])
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(orders) == 1 && system != "auto" {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "Attempting to open with order=%s, system=%s.\n", order, factory.Name())
|
||||
}
|
||||
op, err := factory.Operator(diskbytes, debug)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return op, order, nil
|
||||
}
|
||||
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "Testing whether order=%s, system=%s seems to match.\n", order, factory.Name())
|
||||
}
|
||||
if factory.SeemsToMatch(diskbytes, debug) {
|
||||
op, err := factory.Operator(diskbytes, debug)
|
||||
if err == nil {
|
||||
return op, order, nil
|
||||
}
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "Got error opening with order=%s, system=%s: %v\n", order, factory.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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("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)
|
||||
}
|
||||
|
||||
result := make([]byte, FloppyDiskBytes)
|
||||
for track := 0; track < FloppyTracks; track++ {
|
||||
for sector := 0; sector < FloppySectors; sector++ {
|
||||
data, err := ReadSector(diskimage, byte(track), byte(sector))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = WriteSector(result, byte(track), byte(order[sector]), data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func UnSwizzle(diskimage []byte, order []int) ([]byte, error) {
|
||||
if err := validateOrder(order); err != nil {
|
||||
return nil, fmt.Errorf("called UnSwizzle with weird order: %w", err)
|
||||
}
|
||||
reverseOrder := make([]int, FloppySectors)
|
||||
for index, mapping := range order {
|
||||
reverseOrder[mapping] = index
|
||||
}
|
||||
return Swizzle(diskimage, reverseOrder)
|
||||
}
|
||||
|
||||
// validateOrder validates that an order mapping is valid, and maps [0,15] onto
|
||||
// [0,15] without repeats.
|
||||
func validateOrder(order []int) error {
|
||||
if len(order) != FloppySectors {
|
||||
return fmt.Errorf("len=%d; want %d: %v", len(order), FloppySectors, order)
|
||||
}
|
||||
seen := make(map[int]bool)
|
||||
for i, mapping := range order {
|
||||
if mapping < 0 || mapping > 15 {
|
||||
return fmt.Errorf("mapping %d:%d is not in [0,15]: %v", i, mapping, order)
|
||||
}
|
||||
if seen[mapping] {
|
||||
return fmt.Errorf("mapping %d:%d is a repeat: %v", i, mapping, order)
|
||||
}
|
||||
seen[mapping] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderFromFilename tries to guess the disk order from the filename, using the extension.
|
||||
func OrderFromFilename(filename string, defaultOrder types.DiskOrder) types.DiskOrder {
|
||||
ext := strings.ToLower(path.Ext(filename))
|
||||
switch ext {
|
||||
case ".dsk", ".do":
|
||||
return types.DiskOrderDO
|
||||
case ".po":
|
||||
return types.DiskOrderPO
|
||||
default:
|
||||
return defaultOrder
|
||||
}
|
||||
}
|
||||
|
||||
// WriteBack writes a disk image back out.
|
||||
func WriteBack(filename string, op types.Operator, diskFileOrder types.DiskOrder, overwrite bool) error {
|
||||
logicalBytes := op.GetBytes()
|
||||
// If it's not floppy-sized, we don't swizzle at all.
|
||||
if len(logicalBytes) != FloppyDiskBytes {
|
||||
return helpers.WriteOutput(filename, logicalBytes, overwrite)
|
||||
}
|
||||
|
||||
// Go from logical sectors for the operator back to physical sectors.
|
||||
physicalBytes, err := Swizzle(logicalBytes, LogicalToPhysicalByName[op.DiskOrder()])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Go from physical sectors to the disk order (DO or PO)
|
||||
diskBytes, err := Swizzle(physicalBytes, PhysicalToLogicalByName[diskFileOrder])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return helpers.WriteOutput(filename, diskBytes, overwrite)
|
||||
}
|
|
@ -7,10 +7,10 @@ package dos3
|
|||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -356,11 +356,11 @@ func (fd *FileDesc) FilenameString() string {
|
|||
return strings.TrimRight(string(slice), " ")
|
||||
}
|
||||
|
||||
// descriptor returns a disk.Descriptor for a FileDesc, but with the
|
||||
// descriptor returns a types.Descriptor for a FileDesc, but with the
|
||||
// length set to -1, since we can't know it without reading the file
|
||||
// contents.
|
||||
func (fd FileDesc) descriptor() disk.Descriptor {
|
||||
desc := disk.Descriptor{
|
||||
func (fd FileDesc) descriptor() types.Descriptor {
|
||||
desc := types.Descriptor{
|
||||
Name: fd.FilenameString(),
|
||||
Sectors: int(fd.SectorCount),
|
||||
Length: -1,
|
||||
|
@ -368,28 +368,28 @@ func (fd FileDesc) descriptor() disk.Descriptor {
|
|||
}
|
||||
switch fd.Filetype & 0x7f {
|
||||
case FiletypeText: // Text file
|
||||
desc.Type = disk.FiletypeASCIIText
|
||||
desc.Type = types.FiletypeASCIIText
|
||||
case FiletypeInteger: // INTEGER BASIC file
|
||||
desc.Type = disk.FiletypeIntegerBASIC
|
||||
desc.Type = types.FiletypeIntegerBASIC
|
||||
case FiletypeApplesoft: // APPLESOFT BASIC file
|
||||
desc.Type = disk.FiletypeApplesoftBASIC
|
||||
desc.Type = types.FiletypeApplesoftBASIC
|
||||
case FiletypeBinary: // BINARY file
|
||||
desc.Type = disk.FiletypeBinary
|
||||
desc.Type = types.FiletypeBinary
|
||||
case FiletypeS: // S type file
|
||||
desc.Type = disk.FiletypeS
|
||||
desc.Type = types.FiletypeS
|
||||
case FiletypeRelocatable: // RELOCATABLE object module file
|
||||
desc.Type = disk.FiletypeRelocatable
|
||||
desc.Type = types.FiletypeRelocatable
|
||||
case FiletypeA: // A type file
|
||||
desc.Type = disk.FiletypeNewA
|
||||
desc.Type = types.FiletypeNewA
|
||||
case FiletypeB: // B type file
|
||||
desc.Type = disk.FiletypeNewB
|
||||
desc.Type = types.FiletypeNewB
|
||||
}
|
||||
return desc
|
||||
}
|
||||
|
||||
// Contents returns the on-disk contents of a file represented by a
|
||||
// FileDesc.
|
||||
func (fd *FileDesc) Contents(lsd disk.LogicalSectorDisk) ([]byte, error) {
|
||||
func (fd *FileDesc) Contents(diskbytes []byte) ([]byte, error) {
|
||||
tsls := []TrackSectorList{}
|
||||
nextTrack := fd.TrackSectorListTrack
|
||||
nextSector := fd.TrackSectorListSector
|
||||
|
@ -401,7 +401,7 @@ func (fd *FileDesc) Contents(lsd disk.LogicalSectorDisk) ([]byte, error) {
|
|||
}
|
||||
seen[ts] = true
|
||||
tsl := TrackSectorList{}
|
||||
if err := disk.UnmarshalLogicalSector(lsd, &tsl, nextTrack, nextSector); err != nil {
|
||||
if err := disk.UnmarshalLogicalSector(diskbytes, &tsl, nextTrack, nextSector); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tsls = append(tsls, tsl)
|
||||
|
@ -426,7 +426,7 @@ func (fd *FileDesc) Contents(lsd disk.LogicalSectorDisk) ([]byte, error) {
|
|||
data = append(data, 0)
|
||||
}
|
||||
} else {
|
||||
contents, err := lsd.ReadLogicalSector(ts.Track, ts.Sector)
|
||||
contents, err := disk.ReadSector(diskbytes, ts.Track, ts.Sector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -490,9 +490,9 @@ func (tsl *TrackSectorList) FromSector(data []byte) error {
|
|||
|
||||
// readCatalogSectors reads the raw CatalogSector structs from a DOS
|
||||
// 3.3 disk.
|
||||
func readCatalogSectors(d disk.LogicalSectorDisk) ([]CatalogSector, error) {
|
||||
func readCatalogSectors(diskbytes []byte, debug bool) ([]CatalogSector, error) {
|
||||
v := &VTOC{}
|
||||
err := disk.UnmarshalLogicalSector(d, v, VTOCTrack, VTOCSector)
|
||||
err := disk.UnmarshalLogicalSector(diskbytes, v, VTOCTrack, VTOCSector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -516,7 +516,7 @@ func readCatalogSectors(d disk.LogicalSectorDisk) ([]CatalogSector, error) {
|
|||
return nil, fmt.Errorf("catalog sectors can't be in sector %d: disk only has %d sectors", nextSector, v.NumSectors)
|
||||
}
|
||||
cs := CatalogSector{}
|
||||
err := disk.UnmarshalLogicalSector(d, &cs, nextTrack, nextSector)
|
||||
err := disk.UnmarshalLogicalSector(diskbytes, &cs, nextTrack, nextSector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -528,8 +528,8 @@ func readCatalogSectors(d disk.LogicalSectorDisk) ([]CatalogSector, error) {
|
|||
}
|
||||
|
||||
// ReadCatalog reads the catalog of a DOS 3.3 disk.
|
||||
func ReadCatalog(d disk.LogicalSectorDisk) (files, deleted []FileDesc, err error) {
|
||||
css, err := readCatalogSectors(d)
|
||||
func ReadCatalog(diskbytes []byte, debug bool) (files, deleted []FileDesc, err error) {
|
||||
css, err := readCatalogSectors(diskbytes, debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -549,13 +549,14 @@ func ReadCatalog(d disk.LogicalSectorDisk) (files, deleted []FileDesc, err error
|
|||
return files, deleted, nil
|
||||
}
|
||||
|
||||
// operator is a disk.Operator - an interface for performing
|
||||
// operator is a types.Operator - an interface for performing
|
||||
// high-level operations on files and directories.
|
||||
type operator struct {
|
||||
lsd disk.LogicalSectorDisk
|
||||
data []byte
|
||||
debug bool
|
||||
}
|
||||
|
||||
var _ disk.Operator = operator{}
|
||||
var _ types.Operator = operator{}
|
||||
|
||||
// operatorName is the keyword name for the operator that undestands
|
||||
// dos3 disks.
|
||||
|
@ -566,11 +567,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.lsd.Order()
|
||||
}
|
||||
|
||||
// HasSubdirs returns true if the underlying operating system on the
|
||||
// disk allows subdirectories.
|
||||
func (o operator) HasSubdirs() bool {
|
||||
|
@ -579,12 +575,12 @@ 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) {
|
||||
fds, _, err := ReadCatalog(o.lsd)
|
||||
func (o operator) Catalog(subdir string) ([]types.Descriptor, error) {
|
||||
fds, _, err := ReadCatalog(o.data, o.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
descs := make([]disk.Descriptor, 0, len(fds))
|
||||
descs := make([]types.Descriptor, 0, len(fds))
|
||||
for _, fd := range fds {
|
||||
descs = append(descs, fd.descriptor())
|
||||
}
|
||||
|
@ -594,7 +590,7 @@ func (o operator) Catalog(subdir string) ([]disk.Descriptor, error) {
|
|||
// fileForFilename returns the FileDesc corresponding to the given
|
||||
// filename, or an error.
|
||||
func (o operator) fileForFilename(filename string) (FileDesc, error) {
|
||||
fds, _, err := ReadCatalog(o.lsd)
|
||||
fds, _, err := ReadCatalog(o.data, o.debug)
|
||||
if err != nil {
|
||||
return FileDesc{}, err
|
||||
}
|
||||
|
@ -607,18 +603,18 @@ func (o operator) fileForFilename(filename string) (FileDesc, error) {
|
|||
}
|
||||
|
||||
// GetFile retrieves a file by name.
|
||||
func (o operator) GetFile(filename string) (disk.FileInfo, error) {
|
||||
func (o operator) GetFile(filename string) (types.FileInfo, error) {
|
||||
fd, err := o.fileForFilename(filename)
|
||||
if err != nil {
|
||||
return disk.FileInfo{}, err
|
||||
return types.FileInfo{}, err
|
||||
}
|
||||
desc := fd.descriptor()
|
||||
data, err := fd.Contents(o.lsd)
|
||||
data, err := fd.Contents(o.data)
|
||||
if err != nil {
|
||||
return disk.FileInfo{}, err
|
||||
return types.FileInfo{}, err
|
||||
}
|
||||
|
||||
fi := disk.FileInfo{
|
||||
fi := types.FileInfo{
|
||||
Descriptor: desc,
|
||||
Data: data,
|
||||
}
|
||||
|
@ -659,7 +655,7 @@ func (o operator) GetFile(filename string) (disk.FileInfo, error) {
|
|||
errType = "B"
|
||||
}
|
||||
|
||||
return disk.FileInfo{}, fmt.Errorf("%s does not yet implement `GetFile` for filetype %s", operatorName, errType)
|
||||
return types.FileInfo{}, fmt.Errorf("%s does not yet implement `GetFile` for filetype %s", operatorName, errType)
|
||||
}
|
||||
|
||||
// Delete deletes a file by name. It returns true if the file was
|
||||
|
@ -671,29 +667,46 @@ 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 does not implement PutFile yet", operatorName)
|
||||
}
|
||||
|
||||
// Write writes the underlying disk to the given writer.
|
||||
func (o operator) Write(w io.Writer) (int, error) {
|
||||
return o.lsd.Write(w)
|
||||
// DiskOrder returns the Physical-to-Logical mapping order.
|
||||
func (o operator) DiskOrder() types.DiskOrder {
|
||||
return types.DiskOrderDO
|
||||
}
|
||||
|
||||
// operatorFactory is the factory that returns dos3 operators given
|
||||
// disk images.
|
||||
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
||||
lsd, err := disk.NewMappedDisk(sd, disk.Dos33LogicalToPhysicalSectorMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, err = ReadCatalog(lsd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot read catalog. Underlying error: %v", err)
|
||||
}
|
||||
return operator{lsd: lsd}, nil
|
||||
// GetBytes returns the disk image bytes, in logical order.
|
||||
func (o operator) GetBytes() []byte {
|
||||
return o.data
|
||||
}
|
||||
|
||||
func init() {
|
||||
disk.RegisterDiskOperatorFactory(operatorName, operatorFactory)
|
||||
// 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.
|
||||
_, _, err := ReadCatalog(diskbytes, debug)
|
||||
if 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) {
|
||||
return operator{data: diskbytes, debug: debug}, nil
|
||||
}
|
||||
|
||||
// DiskOrder returns the Physical-to-Logical mapping order.
|
||||
func (of OperatorFactory) DiskOrder() types.DiskOrder {
|
||||
return operator{}.DiskOrder()
|
||||
}
|
|
@ -2,10 +2,9 @@ package dos3
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
)
|
||||
|
||||
// TestVTOCMarshalRoundtrip checks a simple roundtrip of VTOC data.
|
||||
|
@ -76,15 +75,11 @@ func TestTrackSectorListMarshalRoundtrip(t *testing.T) {
|
|||
|
||||
// TestReadCatalog tests the reading of the catalog of a test disk.
|
||||
func TestReadCatalog(t *testing.T) {
|
||||
sd, err := disk.LoadDSK("testdata/dos33test.dsk")
|
||||
diskbytes, err := os.ReadFile("testdata/dos33test.dsk")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dsk, err := disk.NewMappedDisk(sd, disk.Dos33LogicalToPhysicalSectorMap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fds, deleted, err := ReadCatalog(dsk)
|
||||
fds, deleted, err := ReadCatalog(diskbytes, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// This file contains the list of commands to run to re-generate
|
||||
// generated files.
|
||||
|
||||
// Use go-bindata to embed static assets that we need.
|
||||
//go:generate go-bindata -pkg data -prefix "data/" -o data/data.go data/disks data/boot
|
||||
//go:generate goimports -w data/data.go
|
||||
|
||||
package main
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/zellyn/diskii
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kong v0.2.17
|
||||
github.com/kr/pretty v0.2.1
|
||||
github.com/rogpeppe/go-internal v1.8.0
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
github.com/alecthomas/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0=
|
||||
github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// Package helpers contains helper routines for reading and writing files,
|
||||
// allowing `-` to mean stdin/stdout.
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// FileContentsOrStdIn returns the contents of a file, unless the file
|
||||
// is "-", in which case it reads from stdin.
|
||||
func FileContentsOrStdIn(s string) ([]byte, error) {
|
||||
if s == "-" {
|
||||
return io.ReadAll(os.Stdin)
|
||||
}
|
||||
return os.ReadFile(s)
|
||||
}
|
||||
|
||||
func WriteOutput(filename string, contents []byte, force bool) error {
|
||||
if filename == "-" {
|
||||
_, err := os.Stdout.Write(contents)
|
||||
return err
|
||||
}
|
||||
if !force {
|
||||
if _, err := os.Stat(filename); !errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("cannot overwrite file %q without --force (-f)", filename)
|
||||
}
|
||||
}
|
||||
return os.WriteFile(filename, contents, 0666)
|
||||
}
|
304
lib/disk/disk.go
304
lib/disk/disk.go
|
@ -1,304 +0,0 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// Package disk contains routines for reading and writing various disk
|
||||
// file formats.
|
||||
package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Various DOS33 disk characteristics.
|
||||
const (
|
||||
DOS33Tracks = 35
|
||||
DOS33Sectors = 16 // Sectors per track
|
||||
// DOS33DiskBytes is the number of bytes on a DOS 3.3 disk.
|
||||
DOS33DiskBytes = 143360 // 35 tracks * 16 sectors * 256 bytes
|
||||
DOS33TrackBytes = 256 * DOS33Sectors // Bytes per track
|
||||
)
|
||||
|
||||
// Dos33LogicalToPhysicalSectorMap maps logical sector numbers to physical ones.
|
||||
// See [UtA2 9-42 - Read Routines].
|
||||
var Dos33LogicalToPhysicalSectorMap = []byte{
|
||||
0x00, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x01,
|
||||
0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x0F,
|
||||
}
|
||||
|
||||
// Dos33PhysicalToLogicalSectorMap maps physical sector numbers to logical ones.
|
||||
// See [UtA2 9-42 - Read Routines].
|
||||
var Dos33PhysicalToLogicalSectorMap = []byte{
|
||||
0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04,
|
||||
0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F,
|
||||
}
|
||||
|
||||
// ProDOSLogicalToPhysicalSectorMap maps logical sector numbers to pysical ones.
|
||||
// See [UtA2e 9-43 - Sectors vs. Blocks].
|
||||
var ProDOSLogicalToPhysicalSectorMap = []byte{
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
|
||||
0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
}
|
||||
|
||||
// ProDosPhysicalToLogicalSectorMap maps physical sector numbers to logical ones.
|
||||
// See [UtA2e 9-43 - Sectors vs. Blocks].
|
||||
var ProDosPhysicalToLogicalSectorMap = []byte{
|
||||
0x00, 0x08, 0x01, 0x09, 0x02, 0x0A, 0x03, 0x0B,
|
||||
0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F,
|
||||
}
|
||||
|
||||
// TrackSector is a pair of track/sector bytes.
|
||||
type TrackSector struct {
|
||||
Track byte
|
||||
Sector byte
|
||||
}
|
||||
|
||||
// SectorDisk is the interface used to read and write disks by
|
||||
// physical (matches sector header) sector number.
|
||||
type SectorDisk interface {
|
||||
// ReadPhysicalSector reads a single physical sector from the disk. It
|
||||
// always returns 256 byes.
|
||||
ReadPhysicalSector(track byte, sector byte) ([]byte, error)
|
||||
// WritePhysicalSector writes a single physical sector to a disk. It
|
||||
// expects exactly 256 bytes.
|
||||
WritePhysicalSector(track byte, sector byte, data []byte) error
|
||||
// Sectors returns the number of sectors on the SectorDisk
|
||||
Sectors() byte
|
||||
// Tracks returns the number of tracks on the SectorDisk
|
||||
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
|
||||
// *logical* sector number.
|
||||
type LogicalSectorDisk interface {
|
||||
// ReadLogicalSector reads a single logical sector from the disk. It
|
||||
// always returns 256 byes.
|
||||
ReadLogicalSector(track byte, sector byte) ([]byte, error)
|
||||
// WriteLogicalSector writes a single logical sector to a disk. It
|
||||
// expects exactly 256 bytes.
|
||||
WriteLogicalSector(track byte, sector byte, data []byte) error
|
||||
// Sectors returns the number of sectors on the SectorDisk
|
||||
Sectors() byte
|
||||
// Tracks returns the number of tracks on the SectorDisk
|
||||
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
|
||||
// logical-to-physical sector mapping.
|
||||
type MappedDisk struct {
|
||||
sectorDisk SectorDisk // The underlying physical sector disk.
|
||||
logicalToPhysical []byte // The mapping of logical to physical sectors.
|
||||
}
|
||||
|
||||
var _ LogicalSectorDisk = MappedDisk{}
|
||||
|
||||
// NewMappedDisk returns a MappedDisk with the given
|
||||
// logical-to-physical sector mapping.
|
||||
func NewMappedDisk(sd SectorDisk, logicalToPhysical []byte) (MappedDisk, error) {
|
||||
if logicalToPhysical != nil && len(logicalToPhysical) != int(sd.Sectors()) {
|
||||
return MappedDisk{}, fmt.Errorf("NewMappedDisk called on a disk image with %d sectors per track, but a mapping of length %d", sd.Sectors(), len(logicalToPhysical))
|
||||
}
|
||||
if logicalToPhysical == nil {
|
||||
logicalToPhysical = make([]byte, int(sd.Sectors()))
|
||||
for i := range logicalToPhysical {
|
||||
logicalToPhysical[i] = byte(i)
|
||||
}
|
||||
}
|
||||
return MappedDisk{
|
||||
sectorDisk: sd,
|
||||
logicalToPhysical: logicalToPhysical,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadLogicalSector reads a single logical sector from the disk. It
|
||||
// always returns 256 byes.
|
||||
func (md MappedDisk) ReadLogicalSector(track byte, sector byte) ([]byte, error) {
|
||||
if track >= md.sectorDisk.Tracks() {
|
||||
return nil, fmt.Errorf("ReadLogicalSector expected track between 0 and %d; got %d", md.sectorDisk.Tracks()-1, track)
|
||||
}
|
||||
if sector >= md.sectorDisk.Sectors() {
|
||||
return nil, fmt.Errorf("ReadLogicalSector expected sector between 0 and %d; got %d", md.sectorDisk.Sectors()-1, sector)
|
||||
}
|
||||
physicalSector := md.logicalToPhysical[int(sector)]
|
||||
return md.sectorDisk.ReadPhysicalSector(track, physicalSector)
|
||||
}
|
||||
|
||||
// WriteLogicalSector writes a single logical sector to a disk. It
|
||||
// expects exactly 256 bytes.
|
||||
func (md MappedDisk) WriteLogicalSector(track byte, sector byte, data []byte) error {
|
||||
if track >= md.sectorDisk.Tracks() {
|
||||
return fmt.Errorf("WriteLogicalSector expected track between 0 and %d; got %d", md.sectorDisk.Tracks()-1, track)
|
||||
}
|
||||
if sector >= md.sectorDisk.Sectors() {
|
||||
return fmt.Errorf("WriteLogicalSector expected sector between 0 and %d; got %d", md.sectorDisk.Sectors()-1, sector)
|
||||
}
|
||||
physicalSector := md.logicalToPhysical[int(sector)]
|
||||
return md.sectorDisk.WritePhysicalSector(track, physicalSector, data)
|
||||
}
|
||||
|
||||
// Sectors returns the number of sectors in the disk image.
|
||||
func (md MappedDisk) Sectors() byte {
|
||||
return md.sectorDisk.Sectors()
|
||||
}
|
||||
|
||||
// Tracks returns the number of tracks in the disk image.
|
||||
func (md MappedDisk) Tracks() byte {
|
||||
return md.sectorDisk.Tracks()
|
||||
}
|
||||
|
||||
// Write writes the disk contents to the given file.
|
||||
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))
|
||||
switch ext {
|
||||
case ".dsk":
|
||||
return LoadDSK(filename)
|
||||
}
|
||||
return nil, fmt.Errorf("Unimplemented/unknown disk file extension %q", ext)
|
||||
}
|
||||
|
||||
// OpenDev opens a device image by filename.
|
||||
func OpenDev(filename string) (BlockDevice, error) {
|
||||
ext := strings.ToLower(path.Ext(filename))
|
||||
switch ext {
|
||||
case ".po":
|
||||
return LoadDev(filename)
|
||||
}
|
||||
return nil, fmt.Errorf("Unimplemented/unknown device file extension %q", ext)
|
||||
}
|
||||
|
||||
// Open opens a disk image by filename, returning an Operator.
|
||||
func Open(filename string) (Operator, error) {
|
||||
sd, err := OpenDisk(filename)
|
||||
if err == nil {
|
||||
var op Operator
|
||||
op, err = OperatorForDisk(sd)
|
||||
if err == nil {
|
||||
return op, nil
|
||||
}
|
||||
}
|
||||
|
||||
dev, err2 := OpenDev(filename)
|
||||
if err2 == nil {
|
||||
var op Operator
|
||||
op, err2 = OperatorForDevice(dev)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
return op, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type DiskBlockDevice struct {
|
||||
lsd LogicalSectorDisk
|
||||
blocks uint16
|
||||
}
|
||||
|
||||
// BlockDeviceFromSectorDisk creates a ProDOS block device from a
|
||||
// SectorDisk. It reads maps ProDOS to physical sectors.
|
||||
func BlockDeviceFromSectorDisk(sd SectorDisk) (BlockDevice, error) {
|
||||
lsd, err := NewMappedDisk(sd, ProDOSLogicalToPhysicalSectorMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return DiskBlockDevice{
|
||||
lsd: lsd,
|
||||
blocks: uint16(lsd.Tracks()) / 2 * uint16(lsd.Sectors()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadBlock reads a single block from the device. It always returns
|
||||
// 512 byes.
|
||||
func (dbv DiskBlockDevice) ReadBlock(index uint16) (Block, error) {
|
||||
var b Block
|
||||
if index >= dbv.blocks {
|
||||
return b, fmt.Errorf("device has %d blocks; tried to read block %d (index=%d)", dbv.blocks, index+1, index)
|
||||
}
|
||||
i := int(index) * 2
|
||||
sectors := int(dbv.lsd.Sectors())
|
||||
|
||||
track0 := i / sectors
|
||||
sector0 := i % sectors
|
||||
sector1 := sector0 + 1
|
||||
track1 := track0
|
||||
if sector1 == sectors {
|
||||
sector1 = 0
|
||||
track1++
|
||||
}
|
||||
|
||||
b0, err := dbv.lsd.ReadLogicalSector(byte(track0), byte(sector0))
|
||||
if err != nil {
|
||||
return b, fmt.Errorf("error reading first half of block %d (t:%d s:%d): %v", index, track0, sector0, err)
|
||||
}
|
||||
b1, err := dbv.lsd.ReadLogicalSector(byte(track1), byte(sector1))
|
||||
if err != nil {
|
||||
return b, fmt.Errorf("error reading second half of block %d (t:%d s:%d): %v", index, track1, sector1, err)
|
||||
}
|
||||
copy(b[:256], b0)
|
||||
copy(b[256:], b1)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// WriteBlock writes a single block to a device. It expects exactly
|
||||
// 512 bytes.
|
||||
func (dbv DiskBlockDevice) WriteBlock(index uint16, data Block) error {
|
||||
if index >= dbv.blocks {
|
||||
return fmt.Errorf("device has %d blocks; tried to read block %d (index=%d)", dbv.blocks, index+1, index)
|
||||
}
|
||||
i := int(index) * 2
|
||||
sectors := int(dbv.lsd.Sectors())
|
||||
|
||||
track0 := i / sectors
|
||||
sector0 := i % sectors
|
||||
sector1 := sector0 + 1
|
||||
track1 := track0
|
||||
if sector1 == sectors {
|
||||
sector1 = 0
|
||||
track1++
|
||||
}
|
||||
|
||||
if err := dbv.lsd.WriteLogicalSector(byte(track0), byte(sector0), data[:256]); err != nil {
|
||||
return fmt.Errorf("error writing first half of block %d (t:%d s:%d): %v", index, track0, sector0, err)
|
||||
}
|
||||
if err := dbv.lsd.WriteLogicalSector(byte(track1), byte(sector1), data[256:]); err != nil {
|
||||
return fmt.Errorf("error writing second half of block %d (t:%d s:%d): %v", index, track1, sector1, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Blocks returns the number of blocks on the device.
|
||||
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)
|
||||
}
|
111
lib/disk/dsk.go
111
lib/disk/dsk.go
|
@ -1,111 +0,0 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// dsk.go contains logic for reading ".dsk" disk images.
|
||||
|
||||
package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// DSK represents a .dsk disk image.
|
||||
type DSK struct {
|
||||
data []byte // The actual data in the file
|
||||
sectors byte // Number of sectors per track
|
||||
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)
|
||||
|
||||
// LoadDSK loads a .dsk image from a file.
|
||||
func LoadDSK(filename string) (DSK, error) {
|
||||
bb, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return DSK{}, err
|
||||
}
|
||||
// TODO(zellyn): handle 13-sector disks.
|
||||
if len(bb) != DOS33DiskBytes {
|
||||
return DSK{}, fmt.Errorf("expected file %q to contain %d bytes, but got %d", filename, DOS33DiskBytes, len(bb))
|
||||
}
|
||||
return DSK{
|
||||
data: bb,
|
||||
sectors: 16,
|
||||
physicalToStored: Dos33PhysicalToLogicalSectorMap,
|
||||
bytesPerTrack: 16 * 256,
|
||||
tracks: DOS33Tracks,
|
||||
order: "dos33",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Empty creates a .dsk image that is all zeros.
|
||||
func Empty() DSK {
|
||||
return DSK{
|
||||
data: make([]byte, DOS33DiskBytes),
|
||||
sectors: 16,
|
||||
physicalToStored: Dos33PhysicalToLogicalSectorMap,
|
||||
bytesPerTrack: 16 * 256,
|
||||
tracks: DOS33Tracks,
|
||||
order: "dos33",
|
||||
}
|
||||
}
|
||||
|
||||
// ReadPhysicalSector reads a single physical sector from the disk. It
|
||||
// always returns 256 byes.
|
||||
func (d DSK) ReadPhysicalSector(track byte, sector byte) ([]byte, error) {
|
||||
if track >= d.tracks {
|
||||
return nil, fmt.Errorf("ReadPhysicalSector expected track between 0 and %d; got %d", d.tracks-1, track)
|
||||
}
|
||||
if sector >= d.sectors {
|
||||
return nil, fmt.Errorf("ReadPhysicalSector expected sector between 0 and %d; got %d", d.sectors-1, sector)
|
||||
}
|
||||
|
||||
storedSector := d.physicalToStored[int(sector)]
|
||||
start := int(track)*d.bytesPerTrack + 256*int(storedSector)
|
||||
buf := make([]byte, 256)
|
||||
copy(buf, d.data[start:start+256])
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// WritePhysicalSector writes a single physical sector to a disk. It
|
||||
// expects exactly 256 bytes.
|
||||
func (d DSK) WritePhysicalSector(track byte, sector byte, data []byte) error {
|
||||
if track >= d.tracks {
|
||||
return fmt.Errorf("WritePhysicalSector expected track between 0 and %d; got %d", d.tracks-1, track)
|
||||
}
|
||||
if sector >= d.sectors {
|
||||
return fmt.Errorf("WritePhysicalSector expected sector between 0 and %d; got %d", d.sectors-1, sector)
|
||||
}
|
||||
if len(data) != 256 {
|
||||
return fmt.Errorf("WritePhysicalSector expects data of length 256; got %d", len(data))
|
||||
}
|
||||
|
||||
storedSector := d.physicalToStored[int(sector)]
|
||||
start := int(track)*d.bytesPerTrack + 256*int(storedSector)
|
||||
copy(d.data[start:start+256], data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sectors returns the number of sectors on the DSK image.
|
||||
func (d DSK) Sectors() byte {
|
||||
return d.sectors
|
||||
}
|
||||
|
||||
// Tracks returns the number of tracks on the DSK image.
|
||||
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)
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// marshal.go contains helpers for marshaling sector structs to/from
|
||||
// disk and block structs to/from devices.
|
||||
|
||||
package disk
|
||||
|
||||
import "io"
|
||||
|
||||
// A ProDOS block.
|
||||
type Block [512]byte
|
||||
|
||||
// BlockDevice is the interface used to read and write devices by
|
||||
// logical block number.
|
||||
type BlockDevice interface {
|
||||
// ReadBlock reads a single block from the device. It always returns
|
||||
// 512 byes.
|
||||
ReadBlock(index uint16) (Block, error)
|
||||
// WriteBlock writes a single block to a device. It expects exactly
|
||||
// 512 bytes.
|
||||
WriteBlock(index uint16, data Block) error
|
||||
// Blocks returns the number of blocks on the device.
|
||||
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.
|
||||
type SectorSource interface {
|
||||
// ToSector marshals the sector struct to exactly 256 bytes.
|
||||
ToSector() ([]byte, error)
|
||||
// GetTrack returns the track that a sector struct was loaded from.
|
||||
GetTrack() byte
|
||||
// GetSector returns the sector that a sector struct was loaded from.
|
||||
GetSector() byte
|
||||
}
|
||||
|
||||
// SectorSink is the interface for types that can unmarshal from sectors.
|
||||
type SectorSink interface {
|
||||
// FromSector unmarshals the sector struct from bytes. Input is
|
||||
// expected to be exactly 256 bytes.
|
||||
FromSector(data []byte) error
|
||||
// SetTrack sets the track that a sector struct was loaded from.
|
||||
SetTrack(track byte)
|
||||
// SetSector sets the sector that a sector struct was loaded from.
|
||||
SetSector(sector byte)
|
||||
}
|
||||
|
||||
// UnmarshalLogicalSector reads a sector from a SectorDisk, and
|
||||
// unmarshals it into a SectorSink, setting its track and sector.
|
||||
func UnmarshalLogicalSector(d LogicalSectorDisk, ss SectorSink, track, sector byte) error {
|
||||
bytes, err := d.ReadLogicalSector(track, sector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ss.FromSector(bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
ss.SetTrack(track)
|
||||
ss.SetSector(sector)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalLogicalSector marshals a SectorSource to its sector on a
|
||||
// SectorDisk.
|
||||
func MarshalLogicalSector(d LogicalSectorDisk, ss SectorSource) error {
|
||||
track := ss.GetTrack()
|
||||
sector := ss.GetSector()
|
||||
bytes, err := ss.ToSector()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.WriteLogicalSector(track, sector, bytes)
|
||||
}
|
||||
|
||||
// BlockSource is the interface for types that can marshal to blocks.
|
||||
type BlockSource interface {
|
||||
// ToBlock marshals the block struct to exactly 512 bytes.
|
||||
ToBlock() (Block, error)
|
||||
// GetBlock returns the index that a block struct was loaded from.
|
||||
GetBlock() uint16
|
||||
}
|
||||
|
||||
// BlockSink is the interface for types that can unmarshal from blocks.
|
||||
type BlockSink interface {
|
||||
// FromBlock unmarshals the block struct from a Block. Input is
|
||||
// expected to be exactly 512 bytes.
|
||||
FromBlock(block Block) error
|
||||
// SetBlock sets the index that a block struct was loaded from.
|
||||
SetBlock(index uint16)
|
||||
}
|
||||
|
||||
// UnmarshalBlock reads a block from a BlockDevice, and unmarshals it
|
||||
// into a BlockSink, setting its index.
|
||||
func UnmarshalBlock(d BlockDevice, bs BlockSink, index uint16) error {
|
||||
block, err := d.ReadBlock(index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bs.FromBlock(block); err != nil {
|
||||
return err
|
||||
}
|
||||
bs.SetBlock(index)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBlock marshals a BlockSource to its block on a BlockDevice.
|
||||
func MarshalBlock(d BlockDevice, bs BlockSource) error {
|
||||
index := bs.GetBlock()
|
||||
block, err := bs.ToBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.WriteBlock(index, block)
|
||||
}
|
134
lib/disk/ops.go
134
lib/disk/ops.go
|
@ -1,134 +0,0 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// ops.go contains the interfaces and helper functions for operating
|
||||
// on disk images logically: catalog, rename, delete, create files,
|
||||
// etc.
|
||||
|
||||
package disk
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Descriptor describes a file's characteristics.
|
||||
type Descriptor struct {
|
||||
Name string
|
||||
Fullname string // If there's a more complete filename (eg. Super-Mon), put it here.
|
||||
Sectors int
|
||||
Blocks int
|
||||
Length int
|
||||
Locked bool
|
||||
Type Filetype
|
||||
}
|
||||
|
||||
// Operator is the interface that can operate on disks.
|
||||
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
|
||||
// Catalog returns a catalog of disk entries. subdir should be empty
|
||||
// for operating systems that do not support subdirectories.
|
||||
Catalog(subdir string) ([]Descriptor, error)
|
||||
// GetFile retrieves a file by name.
|
||||
GetFile(filename string) (FileInfo, error)
|
||||
// Delete deletes a file by name. It returns true if the file was
|
||||
// deleted, false if it didn't exist.
|
||||
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.
|
||||
PutFile(fileInfo FileInfo, overwrite bool) (existed bool, err error)
|
||||
// Write writes the underlying disk to the given writer.
|
||||
Write(io.Writer) (int, error)
|
||||
}
|
||||
|
||||
// FileInfo represents a file descriptor plus the content.
|
||||
type FileInfo struct {
|
||||
Descriptor Descriptor
|
||||
Data []byte
|
||||
StartAddress uint16
|
||||
}
|
||||
|
||||
// diskOperatorFactory is the type of functions that accept a SectorDisk,
|
||||
// and may return an Operator interface to operate on it.
|
||||
type diskOperatorFactory func(SectorDisk) (Operator, error)
|
||||
|
||||
// diskOperatorFactories is the map of currently-registered disk
|
||||
// operator factories.
|
||||
var diskOperatorFactories map[string]diskOperatorFactory
|
||||
|
||||
func init() {
|
||||
diskOperatorFactories = make(map[string]diskOperatorFactory)
|
||||
}
|
||||
|
||||
// RegisterDiskOperatorFactory registers a disk operator factory with
|
||||
// the given name: a function that accepts a SectorDisk, and may
|
||||
// return an Operator. It doesn't lock diskOperatorFactories: it is
|
||||
// expected to be called only from package `init` functions.
|
||||
func RegisterDiskOperatorFactory(name string, factory diskOperatorFactory) {
|
||||
diskOperatorFactories[name] = factory
|
||||
}
|
||||
|
||||
// OperatorForDisk returns an Operator for the given SectorDisk, if possible.
|
||||
func OperatorForDisk(sd SectorDisk) (Operator, error) {
|
||||
if len(diskOperatorFactories) == 0 {
|
||||
return nil, errors.New("Cannot find an operator matching the given disk image (none registered)")
|
||||
}
|
||||
for _, factory := range diskOperatorFactories {
|
||||
if operator, err := factory(sd); err == nil {
|
||||
return operator, nil
|
||||
}
|
||||
}
|
||||
names := make([]string, 0, len(diskOperatorFactories))
|
||||
for name := range diskOperatorFactories {
|
||||
names = append(names, `"`+name+`"`)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return nil, fmt.Errorf("Cannot find a disk operator matching the given disk image (tried %s)", strings.Join(names, ", "))
|
||||
}
|
||||
|
||||
// deviceOperatorFactory is the type of functions that accept a BlockDevice,
|
||||
// and may return an Operator interface to operate on it.
|
||||
type deviceOperatorFactory func(BlockDevice) (Operator, error)
|
||||
|
||||
// deviceOperatorFactories is the map of currently-registered device
|
||||
// operator factories.
|
||||
var deviceOperatorFactories map[string]deviceOperatorFactory
|
||||
|
||||
func init() {
|
||||
deviceOperatorFactories = make(map[string]deviceOperatorFactory)
|
||||
}
|
||||
|
||||
// RegisterDeviceOperatorFactory registers a device operator factory with
|
||||
// the given name: a function that accepts a BlockDevice, and may
|
||||
// return an Operator. It doesn't lock deviceOperatorFactories: it is
|
||||
// expected to be called only from package `init` functions.
|
||||
func RegisterDeviceOperatorFactory(name string, factory deviceOperatorFactory) {
|
||||
deviceOperatorFactories[name] = factory
|
||||
}
|
||||
|
||||
// OperatorForDevice returns an Operator for the given BlockDevice, if possible.
|
||||
func OperatorForDevice(sd BlockDevice) (Operator, error) {
|
||||
if len(deviceOperatorFactories) == 0 {
|
||||
return nil, errors.New("Cannot find an operator matching the given device image (none registered)")
|
||||
}
|
||||
for _, factory := range deviceOperatorFactories {
|
||||
if operator, err := factory(sd); err == nil {
|
||||
return operator, nil
|
||||
}
|
||||
}
|
||||
names := make([]string, 0, len(deviceOperatorFactories))
|
||||
for name := range deviceOperatorFactories {
|
||||
names = append(names, `"`+name+`"`)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return nil, fmt.Errorf("Cannot find a device operator matching the given device image (tried %s)", strings.Join(names, ", "))
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// Package helpers contains various routines used to help cobra
|
||||
// commands stay succinct.
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// FileContentsOrStdIn returns the contents of a file, unless the file
|
||||
// is "-", in which case it reads from stdin.
|
||||
func FileContentsOrStdIn(s string) ([]byte, error) {
|
||||
if s == "-" {
|
||||
return ioutil.ReadAll(os.Stdin)
|
||||
}
|
||||
return ioutil.ReadFile(s)
|
||||
}
|
92
main.go
92
main.go
|
@ -1,16 +1,92 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
// Copyright ©2021 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/zellyn/diskii/cmd"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
// Import disk operator factories for DOS3 and Super-Mon
|
||||
_ "github.com/zellyn/diskii/lib/dos3"
|
||||
_ "github.com/zellyn/diskii/lib/prodos"
|
||||
_ "github.com/zellyn/diskii/lib/supermon"
|
||||
"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"
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
var cli struct {
|
||||
Debug bool `kong:"short='v',help='Enable debug mode.'"`
|
||||
|
||||
Ls cmd.LsCmd `cmd:"" aliases:"list,cat,catalog" help:"List files/directories on a disk."`
|
||||
Reorder cmd.ReorderCmd `cmd:"" help:"Convert between DOS-order and ProDOS-order disk images."`
|
||||
Filetypes cmd.FiletypesCmd `cmd:"" help:"Print a list of filetypes understood by diskii."`
|
||||
Put cmd.PutCmd `cmd:"" help:"Put the raw contents of a file onto a disk."`
|
||||
Rm cmd.DeleteCmd `cmd:"" aliases:"delete" help:"Delete a file."`
|
||||
Dump cmd.DumpCmd `cmd:"" help:"Dump the raw contents of a file."`
|
||||
Nakedos cmd.NakedOSCmd `cmd:"" help:"Work with NakedOS-format disks."`
|
||||
Mksd cmd.SDCmd `cmd:"" help:"Create a “Standard Delivery” disk image containing a binary."`
|
||||
Applesoft cmd.ApplesoftCmd `cmd:"" help:"Work with Applesoft BASIC files."`
|
||||
}
|
||||
|
||||
func run() error {
|
||||
ctx := kong.Parse(&cli,
|
||||
kong.Name("diskii"),
|
||||
kong.Description("A commandline tool for working with Apple II disk images."),
|
||||
// kong.UsageOnError(),
|
||||
kong.ConfigureHelp(kong.HelpOptions{
|
||||
Compact: true,
|
||||
Summary: true,
|
||||
}),
|
||||
kong.NamedMapper("anybaseuint16", hexUint16Mapper{}),
|
||||
)
|
||||
|
||||
globals := &types.Globals{
|
||||
Debug: cli.Debug,
|
||||
DiskOperatorFactories: []types.OperatorFactory{
|
||||
dos3.OperatorFactory{},
|
||||
supermon.OperatorFactory{},
|
||||
prodos.OperatorFactory{},
|
||||
},
|
||||
}
|
||||
// Call the Run() method of the selected parsed command.
|
||||
return ctx.Run(globals)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
err := run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type hexUint16Mapper struct{}
|
||||
|
||||
func (h hexUint16Mapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("int")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var sv string
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
sv = v
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
sv = fmt.Sprintf("%v", v)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("expected an int but got %q (%T)", t, t.Value)
|
||||
}
|
||||
n, err := strconv.ParseUint(sv, 0, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected a valid %d bit uint but got %q", 16, sv)
|
||||
}
|
||||
target.SetUint(n)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
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
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
# Quick little script to add failing commands to, so I know what I'm working on next.
|
||||
set -euo pipefail
|
||||
set -x
|
||||
# go run . nakedos mkhello supermon-audit-new.dsk DF02
|
||||
# go run . put -f supermon-audit-new.dsk DF02:FWORLD audit.o
|
|
@ -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]]
|
|
@ -7,9 +7,10 @@ package prodos
|
|||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
// Storage types.
|
||||
|
@ -104,11 +105,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 +117,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 +352,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
|
||||
}
|
||||
|
@ -611,11 +612,12 @@ func (sh SubdirectoryHeader) Name() string {
|
|||
// Volume is the in-memory representation of a device's volume
|
||||
// information.
|
||||
type Volume struct {
|
||||
keyBlock *VolumeDirectoryKeyBlock
|
||||
blocks []*VolumeDirectoryBlock
|
||||
bitmap *VolumeBitMap
|
||||
subdirsByBlock map[uint16]*Subdirectory
|
||||
subdirsByName map[string]*Subdirectory
|
||||
keyBlock *VolumeDirectoryKeyBlock // The key block describing the entire volume
|
||||
blocks []*VolumeDirectoryBlock // The blocks in the top-level volume
|
||||
bitmap *VolumeBitMap // Bitmap of which blocks are free
|
||||
subdirsByBlock map[uint16]*Subdirectory // A mapping of block number to subdirectory object
|
||||
subdirsByName map[string]*Subdirectory // a mapping of string to subdirectory object
|
||||
firstSubdirBlocks map[uint16]uint16 // A mapping of later dir/subdir blocks to the first one in the chain
|
||||
}
|
||||
|
||||
// Subdirectory is the in-memory representation of a single
|
||||
|
@ -652,46 +654,79 @@ 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, debug bool) (Volume, error) {
|
||||
v := Volume{
|
||||
keyBlock: &VolumeDirectoryKeyBlock{},
|
||||
subdirsByBlock: make(map[uint16]*Subdirectory),
|
||||
subdirsByName: make(map[string]*Subdirectory),
|
||||
keyBlock: &VolumeDirectoryKeyBlock{},
|
||||
subdirsByBlock: make(map[uint16]*Subdirectory),
|
||||
subdirsByName: make(map[string]*Subdirectory),
|
||||
firstSubdirBlocks: make(map[uint16]uint16),
|
||||
}
|
||||
|
||||
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 debug {
|
||||
// fmt.Fprintf(os.Stderr, "keyblock: %#v\n", v.keyBlock)
|
||||
// }
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// if debug {
|
||||
// fmt.Fprintf(os.Stderr, "volume bitmap: %#v\n", v.bitmap)
|
||||
// }
|
||||
|
||||
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)
|
||||
v.firstSubdirBlocks[block] = keyBlock
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, " firstSubdirBlocks[%d] → %d\n", block, keyBlock)
|
||||
}
|
||||
// if debug {
|
||||
// fmt.Fprintf(os.Stderr, "block: %#v\n", vdb)
|
||||
// }
|
||||
}
|
||||
|
||||
sdds := v.subdirDescriptors()
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "got %d top-level subdir descriptors\n", len(sdds))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
v.subdirsByBlock[sdd.KeyPointer] = &sub
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, " subdirsByBlock[%d] → %q\n", sdd.KeyPointer, sub.keyBlock.Header.Name())
|
||||
}
|
||||
sdds = append(sdds, sub.subdirDescriptors()...)
|
||||
for _, block := range sub.blocks {
|
||||
v.firstSubdirBlocks[block.block] = sdd.KeyPointer
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, " firstSubdirBlocks[%d] → %d\n", block.block, sdd.KeyPointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "got %d total subdir descriptors\n", len(sdds))
|
||||
}
|
||||
|
||||
for _, sd := range v.subdirsByBlock {
|
||||
name := sd.keyBlock.Header.Name()
|
||||
parentName, err := parentDirName(sd.keyBlock.Header.ParentPointer, keyBlock, v.subdirsByBlock)
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "processing subdir %q\n", name)
|
||||
}
|
||||
parentName, err := parentDirName(sd.keyBlock.Header.ParentPointer, keyBlock, v.subdirsByBlock, v.firstSubdirBlocks)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
@ -701,6 +736,9 @@ func readVolume(bd disk.BlockDevice, keyBlock uint16) (Volume, error) {
|
|||
|
||||
v.subdirsByName[name] = sd
|
||||
}
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "HERE2\n")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
|
@ -729,16 +767,23 @@ func (s Subdirectory) subdirDescriptors() []FileDescriptor {
|
|||
return descs
|
||||
}
|
||||
|
||||
// fullDirName returns the full recursive directory name of the given parent directory.
|
||||
func parentDirName(parentDirectoryBlock uint16, keyBlock uint16, subdirMap map[uint16]*Subdirectory) (string, error) {
|
||||
if parentDirectoryBlock == keyBlock {
|
||||
// parentDirName returns the full recursive directory name of the given parent directory.
|
||||
func parentDirName(parentDirectoryBlock uint16, keyBlock uint16, subdirMap map[uint16]*Subdirectory, firstSubdirBlockMap map[uint16]uint16) (string, error) {
|
||||
if parentDirectoryBlock == keyBlock || firstSubdirBlockMap[parentDirectoryBlock] == keyBlock {
|
||||
return "", nil
|
||||
}
|
||||
sd := subdirMap[parentDirectoryBlock]
|
||||
if sd == nil {
|
||||
parentFirstBlock, ok := firstSubdirBlockMap[parentDirectoryBlock]
|
||||
if ok {
|
||||
sd = subdirMap[parentFirstBlock]
|
||||
}
|
||||
}
|
||||
if sd == nil {
|
||||
return "", fmt.Errorf("Unable to find subdirectory for block %d", parentDirectoryBlock)
|
||||
}
|
||||
parentName, err := parentDirName(sd.keyBlock.Header.ParentPointer, keyBlock, subdirMap)
|
||||
|
||||
parentName, err := parentDirName(sd.keyBlock.Header.ParentPointer, keyBlock, subdirMap, firstSubdirBlockMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -751,18 +796,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 +828,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 +843,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 +851,16 @@ 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) {
|
||||
|
||||
vol, err := readVolume(o.dev, 2)
|
||||
func (o operator) Catalog(subdir string) ([]types.Descriptor, error) {
|
||||
if o.debug {
|
||||
fmt.Fprintf(os.Stderr, "Catalog of %q\n", subdir)
|
||||
}
|
||||
vol, err := readVolume(o.data, 2, o.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error reading volume: %w", err)
|
||||
}
|
||||
|
||||
var result []disk.Descriptor
|
||||
var result []types.Descriptor
|
||||
|
||||
if subdir == "" {
|
||||
for _, desc := range vol.descriptors() {
|
||||
|
@ -842,8 +885,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 +898,46 @@ 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)
|
||||
// DiskOrder returns the Physical-to-Logical mapping order.
|
||||
func (o operator) DiskOrder() types.DiskOrder {
|
||||
return types.DiskOrderPO
|
||||
}
|
||||
|
||||
// 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("")
|
||||
// GetBytes returns the disk image bytes, in logical order.
|
||||
func (o operator) GetBytes() []byte {
|
||||
return o.data
|
||||
}
|
||||
|
||||
// OperatorFactory is a types.OperatorFactory for ProDos 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(devicebytes []byte, debug bool) bool {
|
||||
// For now, just return true if we can run Catalog successfully.
|
||||
_, err := readVolume(devicebytes, 2, debug)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot read catalog. Underlying error: %v", err)
|
||||
return false
|
||||
}
|
||||
return op, nil
|
||||
return true
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
return deviceOperatorFactory(bd)
|
||||
// Operator returns an Operator for the []byte disk image.
|
||||
func (of OperatorFactory) Operator(devicebytes []byte, debug bool) (types.Operator, error) {
|
||||
return operator{data: devicebytes, debug: debug}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
disk.RegisterDeviceOperatorFactory(operatorName, deviceOperatorFactory)
|
||||
disk.RegisterDiskOperatorFactory(operatorName, diskOperatorFactory)
|
||||
// DiskOrder returns the Physical-to-Logical mapping order.
|
||||
func (of OperatorFactory) DiskOrder() types.DiskOrder {
|
||||
return operator{}.DiskOrder()
|
||||
}
|
|
@ -6,7 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/kr/pretty"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
)
|
||||
|
||||
func randomBlock() disk.Block {
|
|
@ -0,0 +1,25 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/rogpeppe/go-internal/testscript"
|
||||
)
|
||||
|
||||
func testscriptMain() int {
|
||||
main()
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(testscript.RunMain(m, map[string]func() int{
|
||||
"diskii": testscriptMain,
|
||||
}))
|
||||
}
|
||||
|
||||
func TestFoo(t *testing.T) {
|
||||
testscript.Run(t, testscript.Params{
|
||||
Dir: "testdata",
|
||||
})
|
||||
}
|
|
@ -7,12 +7,12 @@ package supermon
|
|||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/lib/errors"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/errors"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -29,17 +29,17 @@ const (
|
|||
type SectorMap []byte
|
||||
|
||||
// LoadSectorMap loads a NakedOS sector map.
|
||||
func LoadSectorMap(sd disk.SectorDisk) (SectorMap, error) {
|
||||
func LoadSectorMap(diskbytes []byte) (SectorMap, error) {
|
||||
sm := SectorMap(make([]byte, 560))
|
||||
sector09, err := sd.ReadPhysicalSector(0, 9)
|
||||
sector09, err := disk.ReadSector(diskbytes, 0, 9)
|
||||
if err != nil {
|
||||
return sm, err
|
||||
}
|
||||
sector0A, err := sd.ReadPhysicalSector(0, 0xA)
|
||||
sector0A, err := disk.ReadSector(diskbytes, 0, 0xA)
|
||||
if err != nil {
|
||||
return sm, err
|
||||
}
|
||||
sector0B, err := sd.ReadPhysicalSector(0, 0xB)
|
||||
sector0B, err := disk.ReadSector(diskbytes, 0, 0xB)
|
||||
if err != nil {
|
||||
return sm, err
|
||||
}
|
||||
|
@ -63,19 +63,19 @@ func (sm SectorMap) FirstFreeFile() byte {
|
|||
|
||||
// Persist writes the current contenst of a sector map back back to
|
||||
// disk.
|
||||
func (sm SectorMap) Persist(sd disk.SectorDisk) error {
|
||||
sector09, err := sd.ReadPhysicalSector(0, 9)
|
||||
func (sm SectorMap) Persist(diskbytes []byte) error {
|
||||
sector09, err := disk.ReadSector(diskbytes, 0, 9)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy(sector09[0xd0:], sm[0:0x30])
|
||||
if err := sd.WritePhysicalSector(0, 9, sector09); err != nil {
|
||||
if err := disk.WriteSector(diskbytes, 0, 9, sector09); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sd.WritePhysicalSector(0, 0xA, sm[0x30:0x130]); err != nil {
|
||||
if err := disk.WriteSector(diskbytes, 0, 0xA, sm[0x30:0x130]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sd.WritePhysicalSector(0, 0xB, sm[0x130:0x230]); err != nil {
|
||||
if err := disk.WriteSector(diskbytes, 0, 0xB, sm[0x130:0x230]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -167,10 +167,10 @@ func (sm SectorMap) SectorsByFile() map[byte][]disk.TrackSector {
|
|||
}
|
||||
|
||||
// ReadFile reads the contents of a file.
|
||||
func (sm SectorMap) ReadFile(sd disk.SectorDisk, file byte) ([]byte, error) {
|
||||
func (sm SectorMap) ReadFile(diskbytes []byte, file byte) ([]byte, error) {
|
||||
var result []byte
|
||||
for _, ts := range sm.SectorsForFile(file) {
|
||||
bytes, err := sd.ReadPhysicalSector(ts.Track, ts.Sector)
|
||||
bytes, err := disk.ReadSector(diskbytes, ts.Track, ts.Sector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ func (sm SectorMap) Delete(file byte) {
|
|||
|
||||
// WriteFile writes the contents of a file. It returns true if the
|
||||
// file already existed.
|
||||
func (sm SectorMap) WriteFile(sd disk.SectorDisk, file byte, contents []byte, overwrite bool) (bool, error) {
|
||||
func (sm SectorMap) WriteFile(diskbytes []byte, file byte, contents []byte, overwrite bool) (bool, error) {
|
||||
sectorsNeeded := (len(contents) + 255) / 256
|
||||
cts := make([]byte, 256*sectorsNeeded)
|
||||
copy(cts, contents)
|
||||
|
@ -209,10 +209,10 @@ func (sm SectorMap) WriteFile(sd disk.SectorDisk, file byte, contents []byte, ov
|
|||
|
||||
i := 0
|
||||
OUTER:
|
||||
for track := byte(0); track < sd.Tracks(); track++ {
|
||||
for sector := byte(0); sector < sd.Sectors(); sector++ {
|
||||
for track := byte(0); track < disk.FloppyTracks; track++ {
|
||||
for sector := byte(0); sector < disk.FloppySectors; sector++ {
|
||||
if sm.FileForSector(track, sector) == FileFree {
|
||||
if err := sd.WritePhysicalSector(track, sector, cts[i*256:(i+1)*256]); err != nil {
|
||||
if err := disk.WriteSector(diskbytes, track, sector, cts[i*256:(i+1)*256]); err != nil {
|
||||
return existed, err
|
||||
}
|
||||
if err := sm.SetFileForSector(track, sector, file); err != nil {
|
||||
|
@ -225,7 +225,7 @@ OUTER:
|
|||
}
|
||||
}
|
||||
}
|
||||
if err := sm.Persist(sd); err != nil {
|
||||
if err := sm.Persist(diskbytes); err != nil {
|
||||
return existed, err
|
||||
}
|
||||
return existed, nil
|
||||
|
@ -254,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
|
||||
}
|
||||
|
@ -317,16 +317,16 @@ type SymbolTable []Symbol
|
|||
// ReadSymbolTable reads the symbol table from a disk. If there are
|
||||
// problems with the symbol table (like it doesn't exist, or the link
|
||||
// pointers are problematic), it'll return nil and an error.
|
||||
func (sm SectorMap) ReadSymbolTable(sd disk.SectorDisk) (SymbolTable, error) {
|
||||
func (sm SectorMap) ReadSymbolTable(diskbytes []byte) (SymbolTable, error) {
|
||||
table := make(SymbolTable, 0, 819)
|
||||
symtbl1, err := sm.ReadFile(sd, 3)
|
||||
symtbl1, err := sm.ReadFile(diskbytes, 3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(symtbl1) != 0x1000 {
|
||||
return nil, fmt.Errorf("expected file FSYMTBL1(0x3) to be 0x1000 bytes long; got 0x%04X", len(symtbl1))
|
||||
}
|
||||
symtbl2, err := sm.ReadFile(sd, 4)
|
||||
symtbl2, err := sm.ReadFile(diskbytes, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -380,7 +380,7 @@ func (sm SectorMap) ReadSymbolTable(sd disk.SectorDisk) (SymbolTable, error) {
|
|||
}
|
||||
|
||||
// WriteSymbolTable writes a symbol table to a disk.
|
||||
func (sm SectorMap) WriteSymbolTable(sd disk.SectorDisk, st SymbolTable) error {
|
||||
func (sm SectorMap) WriteSymbolTable(diskbytes []byte, st SymbolTable) error {
|
||||
symtbl1 := make([]byte, 0x1000)
|
||||
symtbl2 := make([]byte, 0x1000)
|
||||
for i, sym := range st {
|
||||
|
@ -400,10 +400,10 @@ func (sm SectorMap) WriteSymbolTable(sd disk.SectorDisk, st SymbolTable) error {
|
|||
symtbl1[offset+4] = six[5]
|
||||
copy(symtbl2[offset:offset+5], six)
|
||||
}
|
||||
if _, err := sm.WriteFile(sd, 3, symtbl1, true); err != nil {
|
||||
if _, err := sm.WriteFile(diskbytes, 3, symtbl1, true); err != nil {
|
||||
return fmt.Errorf("unable to write first half of symbol table: %v", err)
|
||||
}
|
||||
if _, err := sm.WriteFile(sd, 4, symtbl2, true); err != nil {
|
||||
if _, err := sm.WriteFile(diskbytes, 4, symtbl2, true); err != nil {
|
||||
return fmt.Errorf("unable to write first second of symbol table: %v", err)
|
||||
}
|
||||
return nil
|
||||
|
@ -643,12 +643,13 @@ func (st SymbolTable) FilesForCompoundName(filename string) (numFile byte, named
|
|||
// Operator is a disk.Operator - an interface for performing
|
||||
// high-level operations on files and directories.
|
||||
type Operator struct {
|
||||
SD disk.SectorDisk
|
||||
SM SectorMap
|
||||
ST SymbolTable
|
||||
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.
|
||||
|
@ -659,11 +660,6 @@ 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 {
|
||||
|
@ -672,47 +668,47 @@ 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) {
|
||||
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.SD, file)
|
||||
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,
|
||||
}
|
||||
|
@ -731,13 +727,13 @@ func (o Operator) Delete(filename string) (bool, error) {
|
|||
}
|
||||
existed := len(o.SM.SectorsForFile(file)) > 0
|
||||
o.SM.Delete(file)
|
||||
if err := o.SM.Persist(o.SD); err != nil {
|
||||
if err := o.SM.Persist(o.data); err != nil {
|
||||
return existed, err
|
||||
}
|
||||
if o.ST != nil {
|
||||
changed := o.ST.DeleteSymbol(filename)
|
||||
if changed {
|
||||
if err := o.SM.WriteSymbolTable(o.SD, o.ST); err != nil {
|
||||
if err := o.SM.WriteSymbolTable(o.data, o.ST); err != nil {
|
||||
return existed, err
|
||||
}
|
||||
}
|
||||
|
@ -748,9 +744,9 @@ 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 {
|
||||
return false, fmt.Errorf("%s: only binary file type supported", operatorName)
|
||||
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; got %q", operatorName, fileInfo.Descriptor.Type)
|
||||
}
|
||||
if fileInfo.Descriptor.Length != len(fileInfo.Data) {
|
||||
return false, fmt.Errorf("mismatch between FileInfo.Descriptor.Length (%d) and actual length of FileInfo.Data field (%d)", fileInfo.Descriptor.Length, len(fileInfo.Data))
|
||||
|
@ -774,7 +770,7 @@ func (o Operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool,
|
|||
return false, fmt.Errorf("all files already used")
|
||||
}
|
||||
}
|
||||
existed, err = o.SM.WriteFile(o.SD, numFile, fileInfo.Data, overwrite)
|
||||
existed, err = o.SM.WriteFile(o.data, numFile, fileInfo.Data, overwrite)
|
||||
if err != nil {
|
||||
return existed, err
|
||||
}
|
||||
|
@ -782,22 +778,49 @@ func (o Operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool,
|
|||
if err := o.ST.AddSymbol(symbol, 0xDF00+uint16(numFile)); err != nil {
|
||||
return existed, err
|
||||
}
|
||||
if err := o.SM.WriteSymbolTable(o.SD, o.ST); err != nil {
|
||||
if err := o.SM.WriteSymbolTable(o.data, o.ST); err != nil {
|
||||
return existed, err
|
||||
}
|
||||
}
|
||||
return existed, nil
|
||||
}
|
||||
|
||||
// Write writes the underlying disk to the given writer.
|
||||
func (o Operator) Write(w io.Writer) (int, error) {
|
||||
return o.SD.Write(w)
|
||||
// DiskOrder returns the Physical-to-Logical mapping order.
|
||||
func (o Operator) DiskOrder() types.DiskOrder {
|
||||
return types.DiskOrderRaw
|
||||
}
|
||||
|
||||
// operatorFactory is the factory that returns supermon operators
|
||||
// given disk images.
|
||||
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
||||
sm, err := LoadSectorMap(sd)
|
||||
// GetBytes returns the disk image bytes, in logical order.
|
||||
func (o Operator) GetBytes() []byte {
|
||||
return o.data
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -805,9 +828,9 @@ func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
op := Operator{SD: sd, SM: sm}
|
||||
op := Operator{data: diskbytes, SM: sm, debug: debug}
|
||||
|
||||
st, err := sm.ReadSymbolTable(sd)
|
||||
st, err := sm.ReadSymbolTable(diskbytes)
|
||||
if err == nil {
|
||||
op.ST = st
|
||||
}
|
||||
|
@ -815,6 +838,7 @@ func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
|||
return op, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
disk.RegisterDiskOperatorFactory(operatorName, operatorFactory)
|
||||
// DiskOrder returns the Physical-to-Logical mapping order.
|
||||
func (of OperatorFactory) DiskOrder() types.DiskOrder {
|
||||
return Operator{}.DiskOrder()
|
||||
}
|
|
@ -3,12 +3,14 @@
|
|||
package supermon
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kr/pretty"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
"github.com/zellyn/diskii/disk"
|
||||
"github.com/zellyn/diskii/types"
|
||||
)
|
||||
|
||||
const testDisk = "testdata/chacha20.dsk"
|
||||
|
@ -27,16 +29,34 @@ 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.OpenFile(f, "do", "nakedos", []types.OperatorFactory{OperatorFactory{}}, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return op, nil
|
||||
}
|
||||
|
||||
// TestReadSectorMap tests the reading of the sector map of a test
|
||||
|
@ -126,11 +146,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 +229,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,
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
# hello world
|
||||
exec cat hello.text
|
||||
stdout 'hello world\n'
|
||||
! stderr .
|
||||
|
||||
-- hello.text --
|
||||
hello world
|
|
@ -3,7 +3,7 @@
|
|||
// filetype.go contains the Filetype type, along with routines for
|
||||
// converting to and from strings.
|
||||
|
||||
package disk
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
|
@ -57,62 +57,62 @@ const (
|
|||
// | C0-EE | ProDOS | ProDOS reserved for future use
|
||||
)
|
||||
|
||||
// filetypeInfo holds name information about filetype constants.
|
||||
type filetypeInfo struct {
|
||||
// FiletypeInfo holds name information about filetype constants.
|
||||
type FiletypeInfo struct {
|
||||
Type Filetype // The type itself
|
||||
Name string // The constant name, without the "Filetype" prefix
|
||||
ThreeLetter string // The three-letter abbreviation (ProDOS)
|
||||
OneLetter string // The one-letter abbreviation (DOS 3.x)
|
||||
Desc string // The description of the type
|
||||
Stringified string // (Generated) result of calling String() on the Constant
|
||||
Extra bool // If true, exclude from normal display listing
|
||||
|
||||
Stringified string // (Generated) result of calling String() on the Constant
|
||||
NamesString string // (Generated) the names usable for this filetype.
|
||||
}
|
||||
|
||||
// names of Filetype constants above
|
||||
var filetypeInfos = []filetypeInfo{
|
||||
{FiletypeTypeless, "Typeless", "", "", "Typeless file", "", false},
|
||||
{FiletypeBadBlocks, "BadBlocks", "", "", "Bad blocks file", "", false},
|
||||
{FiletypeSOSPascalCode, "SOSPascalCode", "", "", "PASCAL code file", "", true},
|
||||
{FiletypeSOSPascalText, "SOSPascalText", "", "", "PASCAL text file", "", true},
|
||||
{FiletypeASCIIText, "ASCIIText", "T", "TXT", "ASCII text file", "", false},
|
||||
{FiletypeSOSPascalText2, "SOSPascalText2", "", "", "PASCAL text file", "", true},
|
||||
{FiletypeBinary, "Binary", "B", "BIN", "Binary file", "", false},
|
||||
{FiletypeFont, "Font", "", "", "Font file", "", true},
|
||||
{FiletypeGraphicsScreen, "GraphicsScreen", "", "", "Graphics screen file", "", true},
|
||||
{FiletypeBusinessBASIC, "BusinessBASIC", "", "", "Business BASIC program file", "", true},
|
||||
{FiletypeBusinessBASICData, "BusinessBASICData", "", "", "Business BASIC data file", "", true},
|
||||
{FiletypeSOSWordProcessor, "SOSWordProcessor", "", "", "Word processor file", "", true},
|
||||
{FiletypeSOSSystem, "SOSSystem", "", "", "SOS system file", "", true},
|
||||
{FiletypeDirectory, "Directory", "", "DIR", "Directory file", "", false},
|
||||
{FiletypeRPSData, "RPSData", "", "", "RPS data file", "", true},
|
||||
{FiletypeRPSIndex, "RPSIndex", "", "", "RPS index file", "", true},
|
||||
{FiletypeAppleWorksDatabase, "AppleWorksDatabase", "", "ADB", "AppleWorks data base file", "", false},
|
||||
{FiletypeAppleWorksWordProcessor, "AppleWorksWordProcessor", "", "AWP", "AppleWorks word processing file", "", false},
|
||||
{FiletypeAppleWorksSpreadsheet, "AppleWorksSpreadsheet", "", "ASP", "AppleWorks spreadsheet file", "", false},
|
||||
{FiletypePascal, "Pascal", "", "PAS", "ProDOS PASCAL file", "", false},
|
||||
{FiletypeCommand, "Command", "", "CMD", "Added command file", "", false},
|
||||
{FiletypeUserDefinedF1, "UserDefinedF1", "", "", "ProDOS user defined file type F1", "", true},
|
||||
{FiletypeUserDefinedF2, "UserDefinedF2", "", "", "ProDOS user defined file type F2", "", true},
|
||||
{FiletypeUserDefinedF3, "UserDefinedF3", "", "", "ProDOS user defined file type F3", "", true},
|
||||
{FiletypeUserDefinedF4, "UserDefinedF4", "", "", "ProDOS user defined file type F4", "", true},
|
||||
{FiletypeUserDefinedF5, "UserDefinedF5", "", "", "ProDOS user defined file type F5", "", true},
|
||||
{FiletypeUserDefinedF6, "UserDefinedF6", "", "", "ProDOS user defined file type F6", "", true},
|
||||
{FiletypeUserDefinedF7, "UserDefinedF7", "", "", "ProDOS user defined file type F7", "", true},
|
||||
{FiletypeUserDefinedF8, "UserDefinedF8", "", "", "ProDOS user defined file type F8", "", true},
|
||||
{FiletypeIntegerBASIC, "IntegerBASIC", "I", "INT", "Integer BASIC program file", "", false},
|
||||
{FiletypeIntegerBASICVariables, "IntegerBASICVariables", "", "IVR", "Integer BASIC variables file", "", false},
|
||||
{FiletypeApplesoftBASIC, "ApplesoftBASIC", "A", "BAS", "Applesoft BASIC program file", "", false},
|
||||
{FiletypeApplesoftBASICVariables, "ApplesoftBASICVariables", "", "VAR", "Applesoft BASIC variables file", "", false},
|
||||
{FiletypeRelocatable, "Relocatable", "R", "REL", "EDASM relocatable object module file", "", false},
|
||||
{FiletypeSystem, "System", "", "SYS", "System file", "", false},
|
||||
{FiletypeS, "S", "", "S", `DOS 3.3 Type "S"`, "", false},
|
||||
{FiletypeNewA, "NewA", "", "A", `DOS 3.3 Type "new A"`, "", false},
|
||||
{FiletypeNewB, "NewB", "", "B", `DOS 3.3 Type "new B"`, "", false},
|
||||
var filetypeInfos = []FiletypeInfo{
|
||||
{Type: FiletypeTypeless, Name: "Typeless", Desc: "Typeless file"},
|
||||
{Type: FiletypeBadBlocks, Name: "BadBlocks", Desc: "Bad blocks file"},
|
||||
{Type: FiletypeSOSPascalCode, Name: "SOSPascalCode", Desc: "PASCAL code file", Extra: true},
|
||||
{Type: FiletypeSOSPascalText, Name: "SOSPascalText", Desc: "PASCAL text file", Extra: true},
|
||||
{Type: FiletypeASCIIText, Name: "ASCIIText", ThreeLetter: "TXT", OneLetter: "T", Desc: "ASCII text file"},
|
||||
{Type: FiletypeSOSPascalText2, Name: "SOSPascalText2", Desc: "PASCAL text file", Extra: true},
|
||||
{Type: FiletypeBinary, Name: "Binary", ThreeLetter: "BIN", OneLetter: "B", Desc: "Binary file"},
|
||||
{Type: FiletypeFont, Name: "Font", Desc: "Font file", Extra: true},
|
||||
{Type: FiletypeGraphicsScreen, Name: "GraphicsScreen", Desc: "Graphics screen file", Extra: true},
|
||||
{Type: FiletypeBusinessBASIC, Name: "BusinessBASIC", Desc: "Business BASIC program file", Extra: true},
|
||||
{Type: FiletypeBusinessBASICData, Name: "BusinessBASICData", Desc: "Business BASIC data file", Extra: true},
|
||||
{Type: FiletypeSOSWordProcessor, Name: "SOSWordProcessor", Desc: "Word processor file", Extra: true},
|
||||
{Type: FiletypeSOSSystem, Name: "SOSSystem", Desc: "SOS system file", Extra: true},
|
||||
{Type: FiletypeDirectory, Name: "Directory", ThreeLetter: "DIR", OneLetter: "D", Desc: "Directory file"},
|
||||
{Type: FiletypeRPSData, Name: "RPSData", Desc: "RPS data file", Extra: true},
|
||||
{Type: FiletypeRPSIndex, Name: "RPSIndex", Desc: "RPS index file", Extra: true},
|
||||
{Type: FiletypeAppleWorksDatabase, Name: "AppleWorksDatabase", ThreeLetter: "ADB", Desc: "AppleWorks data base file"},
|
||||
{Type: FiletypeAppleWorksWordProcessor, Name: "AppleWorksWordProcessor", ThreeLetter: "AWP", Desc: "AppleWorks word processing file"},
|
||||
{Type: FiletypeAppleWorksSpreadsheet, Name: "AppleWorksSpreadsheet", ThreeLetter: "ASP", Desc: "AppleWorks spreadsheet file"},
|
||||
{Type: FiletypePascal, Name: "Pascal", ThreeLetter: "PAS", Desc: "ProDOS PASCAL file"},
|
||||
{Type: FiletypeCommand, Name: "Command", ThreeLetter: "CMD", Desc: "Added command file"},
|
||||
{Type: FiletypeUserDefinedF1, Name: "UserDefinedF1", Desc: "ProDOS user defined file type F1", Extra: true},
|
||||
{Type: FiletypeUserDefinedF2, Name: "UserDefinedF2", Desc: "ProDOS user defined file type F2", Extra: true},
|
||||
{Type: FiletypeUserDefinedF3, Name: "UserDefinedF3", Desc: "ProDOS user defined file type F3", Extra: true},
|
||||
{Type: FiletypeUserDefinedF4, Name: "UserDefinedF4", Desc: "ProDOS user defined file type F4", Extra: true},
|
||||
{Type: FiletypeUserDefinedF5, Name: "UserDefinedF5", Desc: "ProDOS user defined file type F5", Extra: true},
|
||||
{Type: FiletypeUserDefinedF6, Name: "UserDefinedF6", Desc: "ProDOS user defined file type F6", Extra: true},
|
||||
{Type: FiletypeUserDefinedF7, Name: "UserDefinedF7", Desc: "ProDOS user defined file type F7", Extra: true},
|
||||
{Type: FiletypeUserDefinedF8, Name: "UserDefinedF8", Desc: "ProDOS user defined file type F8", Extra: true},
|
||||
{Type: FiletypeIntegerBASIC, Name: "IntegerBASIC", ThreeLetter: "INT", OneLetter: "I", Desc: "Integer BASIC program file"},
|
||||
{Type: FiletypeIntegerBASICVariables, Name: "IntegerBASICVariables", ThreeLetter: "IVR", Desc: "Integer BASIC variables file"},
|
||||
{Type: FiletypeApplesoftBASIC, Name: "ApplesoftBASIC", ThreeLetter: "BAS", OneLetter: "A", Desc: "Applesoft BASIC program file"},
|
||||
{Type: FiletypeApplesoftBASICVariables, Name: "ApplesoftBASICVariables", ThreeLetter: "VAR", Desc: "Applesoft BASIC variables file"},
|
||||
{Type: FiletypeRelocatable, Name: "Relocatable", ThreeLetter: "REL", OneLetter: "R", Desc: "EDASM relocatable object module file"},
|
||||
{Type: FiletypeSystem, Name: "System", ThreeLetter: "SYS", Desc: "System file"},
|
||||
{Type: FiletypeS, Name: "S", OneLetter: "S", Desc: `DOS 3.3 Type "S"`},
|
||||
{Type: FiletypeNewA, Name: "NewA", OneLetter: "A", Desc: `DOS 3.3 Type "new A"`},
|
||||
{Type: FiletypeNewB, Name: "NewB", OneLetter: "B", Desc: `DOS 3.3 Type "new B"`},
|
||||
}
|
||||
|
||||
var filetypeInfosMap map[Filetype]filetypeInfo
|
||||
var filetypeNames []string
|
||||
var filetypeNamesExtras []string
|
||||
var filetypeInfosMap map[Filetype]FiletypeInfo
|
||||
|
||||
func init() {
|
||||
sosReserved := []Filetype{0x0D, 0x0E, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}
|
||||
|
@ -124,7 +124,7 @@ func init() {
|
|||
prodosReserved = append(prodosReserved, i)
|
||||
}
|
||||
for _, typ := range sosReserved {
|
||||
info := filetypeInfo{
|
||||
info := FiletypeInfo{
|
||||
Type: typ,
|
||||
Name: fmt.Sprintf("SOSReserved%02X", int(typ)),
|
||||
ThreeLetter: "",
|
||||
|
@ -135,7 +135,7 @@ func init() {
|
|||
filetypeInfos = append(filetypeInfos, info)
|
||||
}
|
||||
for _, typ := range prodosReserved {
|
||||
info := filetypeInfo{
|
||||
info := FiletypeInfo{
|
||||
Type: typ,
|
||||
Name: fmt.Sprintf("ProDOSReserved%02X", int(typ)),
|
||||
ThreeLetter: "",
|
||||
|
@ -147,25 +147,24 @@ func init() {
|
|||
}
|
||||
|
||||
seen := map[string]bool{}
|
||||
filetypeInfosMap = make(map[Filetype]filetypeInfo, len(filetypeInfos))
|
||||
filetypeInfosMap = make(map[Filetype]FiletypeInfo, len(filetypeInfos))
|
||||
for i, info := range filetypeInfos {
|
||||
info.Stringified = info.Desc + " (" + info.Name
|
||||
info.NamesString = info.Name
|
||||
if info.ThreeLetter != "" && !seen[info.ThreeLetter] {
|
||||
info.Stringified += "|" + info.ThreeLetter
|
||||
info.NamesString += "|" + info.ThreeLetter
|
||||
seen[info.ThreeLetter] = true
|
||||
}
|
||||
if info.OneLetter != "" && info.OneLetter != info.Name && !seen[info.OneLetter] {
|
||||
info.Stringified += "|" + info.OneLetter
|
||||
info.NamesString += "|" + info.OneLetter
|
||||
seen[info.OneLetter] = true
|
||||
}
|
||||
info.Stringified += ")"
|
||||
|
||||
filetypeInfos[i] = info
|
||||
filetypeInfosMap[info.Type] = info
|
||||
filetypeNamesExtras = append(filetypeNamesExtras, info.Stringified)
|
||||
if !info.Extra {
|
||||
filetypeNames = append(filetypeNames, info.Stringified)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,10 +186,16 @@ func FiletypeForName(name string) (Filetype, error) {
|
|||
return 0, fmt.Errorf("Unknown Filetype: %q", name)
|
||||
}
|
||||
|
||||
// FiletypeNames returns a list of all filetype names.
|
||||
func FiletypeNames(all bool) []string {
|
||||
// FiletypeInfos returns a list information on all filetypes.
|
||||
func FiletypeInfos(all bool) []FiletypeInfo {
|
||||
if all {
|
||||
return filetypeNamesExtras
|
||||
return filetypeInfos
|
||||
}
|
||||
return filetypeNames
|
||||
var result []FiletypeInfo
|
||||
for _, info := range filetypeInfos {
|
||||
if !info.Extra {
|
||||
result = append(result, info)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// ops.go contains the interfaces and helper functions for operating
|
||||
// on disk images logically: catalog, rename, delete, create files,
|
||||
// etc.
|
||||
|
||||
package types
|
||||
|
||||
// Descriptor describes a file's characteristics.
|
||||
type Descriptor struct {
|
||||
Name string
|
||||
Fullname string // If there's a more complete filename (eg. Super-Mon), put it here.
|
||||
Sectors int
|
||||
Blocks int
|
||||
Length int
|
||||
Locked bool
|
||||
Type Filetype
|
||||
}
|
||||
|
||||
type DiskOrder string
|
||||
|
||||
const (
|
||||
DiskOrderDO = DiskOrder("do")
|
||||
DiskOrderPO = DiskOrder("po")
|
||||
DiskOrderRaw = DiskOrder("raw")
|
||||
DiskOrderAuto = DiskOrder("auto")
|
||||
DiskOrderUnknown = DiskOrder("")
|
||||
)
|
||||
|
||||
// OperatorFactory is the interface for getting operators, and finding out a bit
|
||||
// about them before getting them.
|
||||
type OperatorFactory interface {
|
||||
// Name returns the name of the operator.
|
||||
Name() string
|
||||
// DiskOrder returns the Physical-to-Logical mapping order.
|
||||
DiskOrder() DiskOrder
|
||||
// SeemsToMatch returns true if the []byte disk image seems to match the
|
||||
// system of this operator.
|
||||
SeemsToMatch(diskbytes []byte, debug bool) bool
|
||||
// Operator returns an Operator for the []byte disk image.
|
||||
Operator(diskbytes []byte, debug bool) (Operator, error)
|
||||
}
|
||||
|
||||
// Operator is the interface that can operate on disks.
|
||||
type Operator interface {
|
||||
// Name returns the name of the operator.
|
||||
Name() string
|
||||
// DiskOrder returns the Physical-to-Logical mapping order.
|
||||
DiskOrder() DiskOrder
|
||||
// HasSubdirs returns true if the underlying operating system on the
|
||||
// disk allows subdirectories.
|
||||
HasSubdirs() bool
|
||||
// Catalog returns a catalog of disk entries. subdir should be empty
|
||||
// for operating systems that do not support subdirectories.
|
||||
Catalog(subdir string) ([]Descriptor, error)
|
||||
// GetFile retrieves a file by name.
|
||||
GetFile(filename string) (FileInfo, error)
|
||||
// Delete deletes a file by name. It returns true if the file was
|
||||
// deleted, false if it didn't exist.
|
||||
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.
|
||||
PutFile(fileInfo FileInfo, overwrite bool) (existed bool, err error)
|
||||
// GetBytes returns the disk image bytes, in logical order.
|
||||
GetBytes() []byte
|
||||
}
|
||||
|
||||
// FileInfo represents a file descriptor plus the content.
|
||||
type FileInfo struct {
|
||||
Descriptor Descriptor
|
||||
Data []byte
|
||||
StartAddress uint16
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package types
|
||||
|
||||
// Globals holds flags and configuration that are shared globally.
|
||||
type Globals struct {
|
||||
Debug bool
|
||||
|
||||
DiskOperatorFactories []OperatorFactory
|
||||
}
|
|
@ -5,12 +5,11 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/zellyn/diskii/data"
|
||||
"github.com/zellyn/diskii/lib/woz"
|
||||
"github.com/zellyn/diskii/woz"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
* = $6000
|
||||
START = *
|
||||
|
||||
;; Free addresses: 6, 7, 8, 9
|
||||
|
||||
IOB = $6
|
||||
RWTS = $3D9
|
||||
GET_IOB = $3E3
|
||||
BUF = $6100
|
||||
|
||||
start:
|
||||
; set up IOB address
|
||||
jsr GET_IOB
|
||||
sta IOB+1
|
||||
sty IOB
|
||||
|
||||
lda #$f
|
||||
outer:
|
||||
ldy #0
|
||||
inner:
|
||||
sta BUF,Y
|
||||
iny
|
||||
bne inner
|
||||
pha
|
||||
jsr write
|
||||
pla
|
||||
tax
|
||||
dex
|
||||
txa
|
||||
bpl outer
|
||||
rts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
write:
|
||||
ldy #$5 ; sector number
|
||||
sta ($6), Y
|
||||
ldy #$2 ; drive number
|
||||
lda #$2 ; drive 2
|
||||
sta ($6), Y
|
||||
iny ; 3: volume
|
||||
lda #0 ; any volume
|
||||
sta ($6), Y
|
||||
iny ; 4: track
|
||||
sta ($6), Y
|
||||
ldy #$8 ; LO of buffer
|
||||
lda #<BUF
|
||||
sta ($6), Y
|
||||
iny ; $9: HI of buffer
|
||||
lda #>BUF
|
||||
sta ($6), Y
|
||||
iny ; $A: unused
|
||||
iny ; $B: Byte count
|
||||
lda #0
|
||||
sta ($6),Y
|
||||
iny ; $C: command
|
||||
lda #$02 ; write
|
||||
sta ($6),Y
|
||||
lda IOB+1
|
||||
ldy IOB
|
||||
jsr RWTS
|
||||
rts
|
|
@ -0,0 +1 @@
|
|||
6000:20 e3 03 85 07 84 06 a9 0f a0 00 99 00 61 c8 d0 fa 48 20 1c 60 68 aa ca 8a 10 ee 60 a0 05 91 06 a0 02 a9 02 91 06 c8 a9 00 91 06 c8 91 06 a0 08 a9 00 91 06 c8 a9 61 91 06 c8 c8 a9 00 91 06 c8 a9 02 91 06 a5 07 a4 06 20 d9 03 60
|
Loading…
Reference in New Issue