mirror of
https://github.com/zellyn/diskii.git
synced 2024-06-08 13:29:28 +00:00
Merge pull request #2 from zellyn/refactor
Refactor to use kong instead of cobra
This commit is contained in:
commit
7d036244af
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
|
@ -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
|
57
build
Executable file
57
build
Executable file
|
@ -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
|
||||
}
|
||||
|
|
127
cmd/nakedos.go
127
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}
|
||||
0x2C, 0x00, byte(m.Address >> 8), // BIT ${target page}
|
||||
0xD8, // CLD
|
||||
0x4C, byte(start), byte(start >> 8), // JMP ${target page}
|
||||
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)
|
||||
}
|
||||
|
|
81
cmd/put.go
81
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.
|
||||
|
||||
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)
|
||||
}
|
||||
},
|
||||
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.'"`
|
||||
}
|
||||
|
||||
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")
|
||||
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`
|
||||
}
|
||||
|
||||
// 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>")
|
||||
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")
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
93
cmd/reorder.go
Normal file
93
cmd/reorder.go
Normal file
|
@ -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.
BIN
data/disks/ProDOS_2_4_2.po
Normal file
BIN
data/disks/ProDOS_2_4_2.po
Normal file
Binary file not shown.
BIN
data/disks/Super-Mon-2.0.dsk
Normal file
BIN
data/disks/Super-Mon-2.0.dsk
Normal file
Binary file not shown.
1
data/disks/blank.dsk
Normal file
1
data/disks/blank.dsk
Normal file
File diff suppressed because one or more lines are too long
BIN
data/disks/dos33master.dsk
Normal file
BIN
data/disks/dos33master.dsk
Normal file
Binary file not shown.
BIN
data/disks/prodos-users-disk.dsk
Normal file
BIN
data/disks/prodos-users-disk.dsk
Normal file
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)
|
67
disk/disk.go
Normal file
67
disk/disk.go
Normal file
|
@ -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
|
||||
}
|
135
disk/marshal.go
Normal file
135
disk/marshal.go
Normal file
|
@ -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
|
||||
}
|
240
disk/open.go
Normal file
240
disk/open.go
Normal file
|
@ -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
|
9
go.mod
Normal file
9
go.mod
Normal file
|
@ -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
|
||||
)
|
30
go.sum
Normal file
30
go.sum
Normal file
|
@ -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=
|
35
helpers/helpers.go
Normal file
35
helpers/helpers.go
Normal file
|
@ -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
|
6
next
Executable file
6
next
Executable file
|
@ -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),
|
||||
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 {
|
25
script_test.go
Normal file
25
script_test.go
Normal file
|
@ -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
|
||||
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,
|
||||
}
|
7
testdata/cathello.txt
vendored
Normal file
7
testdata/cathello.txt
vendored
Normal file
|
@ -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
|
||||
}
|
74
types/ops.go
Normal file
74
types/ops.go
Normal file
|
@ -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
|
||||
}
|
8
types/types.go
Normal file
8
types/types.go
Normal file
|
@ -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)
|
||||
}
|
64
writetest.asm
Normal file
64
writetest.asm
Normal file
|
@ -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
|
1
writetest.mon
Normal file
1
writetest.mon
Normal file
|
@ -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
Block a user